From 96f10a07ce3794d659ed3a2f023f537aa5c53d0b Mon Sep 17 00:00:00 2001 From: taefi Date: Mon, 13 Jul 2026 15:16:24 +0300 Subject: [PATCH 1/4] docs: add SwingBridge logging and user identification guide New page (order 5) covering per-session log attribution: quick setup, an integration-level ladder from zero cooperation to the interop APIs, console prefixing and the Log4j2/Logback/JUL adapters, publishing the user identity for both A&A placements (login inside Swing vs. extracted to Vaadin), the reportIncident support workflow, and a section on old and new logging frameworks coexisting (Log4j 1.x bridging, guest config shadowing, Spring Boot Logback vs. Log4j2, SLF4J provider pinning). Also: configuration.adoc gains the swingbridge.consoleLogPrefix and swingbridge.log.user rows; installation-from-scratch.adoc explains its log4j-slf4j2-impl exclusion and links the trade-offs; the Vale vocabulary accepts appender(s), Splunk, and interop (clears 13 pre-existing spelling errors in the interop section). --- .../config/vocabularies/Docs/accept.txt | 6 +- .../swing-bridge/configuration.adoc | 8 + .../installation-from-scratch.adoc | 5 + .../swing-bridge/logging.adoc | 408 ++++++++++++++++++ 4 files changed, 424 insertions(+), 3 deletions(-) create mode 100644 articles/tools/modernization-toolkit/swing-bridge/logging.adoc diff --git a/.github/styles/config/vocabularies/Docs/accept.txt b/.github/styles/config/vocabularies/Docs/accept.txt index b11856162b..18a28bb4e2 100644 --- a/.github/styles/config/vocabularies/Docs/accept.txt +++ b/.github/styles/config/vocabularies/Docs/accept.txt @@ -259,7 +259,6 @@ Workbox WSL Xcode XPath - # Accepted abbreviations whose plural and/or possessive forms otherwise give an error API APIs @@ -282,13 +281,14 @@ VAPID VUs XHR XSS - # Names of React hooks useForm - # Layout and CSS terms [cC]olspans? [rR]eflows? varargs XLarge XXLarge +appenders? +Splunk +[Ii]nterop diff --git a/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc b/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc index dfc47b53f5..2f87cc6817 100644 --- a/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc +++ b/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc @@ -44,6 +44,14 @@ This page lists the runtime knobs SwingBridge reads from JVM system properties, |`swingbridge.errorReporting.enabled` |`true` |When set to `false`, the launch-failure error view shows only the failure header — the exception type, message, and stack trace are suppressed and the report-submission form is hidden. See <<#swing-bridge.configuration.error-reporting, Launch Failure Error View>> below. + +|`swingbridge.consoleLogPrefix` +|`false` +|When set to `true`, every console line written by an embedded Swing application is prefixed with that instance's run ID (`[swing:]`), so concurrent users' output can be told apart in the server console. See <>. + +|`swingbridge.log.user` +|— +|Not set by the operator: an embedded Swing application sets this property at runtime, after its own login, to publish the authenticated user's name for log attribution. The value is routed per session, so concurrent users can't overwrite each other. See <>. |=== For Spring Boot, set system properties through `` on the `spring-boot-maven-plugin` (see <>) or pass `-D` flags on the command line. For a packaged JAR, pass them with `-D` on the command line: diff --git a/articles/tools/modernization-toolkit/swing-bridge/installation-from-scratch.adoc b/articles/tools/modernization-toolkit/swing-bridge/installation-from-scratch.adoc index 4e871ae468..77e5d30a4d 100644 --- a/articles/tools/modernization-toolkit/swing-bridge/installation-from-scratch.adoc +++ b/articles/tools/modernization-toolkit/swing-bridge/installation-from-scratch.adoc @@ -120,6 +120,9 @@ Add the following parent section, properties, and dependencies to your `pom.xml` swing-bridge-graphics ${swing-bridge.version} + com.vaadin swing-bridge-flow @@ -148,6 +151,8 @@ Add the following parent section, properties, and dependencies to your `pom.xml` ---- +The `log4j-slf4j2-impl` exclusion keeps Spring Boot's default Logback as the logging backend. See <> for the trade-offs, and for standardizing on Log4j2 instead so that embedded Swing applications' direct Log4j calls end up in the same log files. + SwingBridge requires certain JVM flags so that Maven can access internal Java modules during compilation. Create a `.mvn/jvm.config` file in the project root with the following content: .`.mvn/jvm.config` diff --git a/articles/tools/modernization-toolkit/swing-bridge/logging.adoc b/articles/tools/modernization-toolkit/swing-bridge/logging.adoc new file mode 100644 index 0000000000..09fe110e76 --- /dev/null +++ b/articles/tools/modernization-toolkit/swing-bridge/logging.adoc @@ -0,0 +1,408 @@ +--- +title: Logging pass:[&] User Identification +page-title: Identify users in SwingBridge logs | Vaadin Tools +description: Attribute server log output to the Swing application user who produced it, and integrate old and new logging frameworks. +meta-description: Tag SwingBridge log output per user session, publish the logged-in user from Swing or Vaadin, and integrate Log4j, SLF4J, and Spring Boot logging. +order: 5 +--- + +include::{articles}/_vaadin-version.adoc[] + +[[swing-bridge.logging]] += Logging & User Identification + +On the desktop, one Swing application serves one user, so its log file belongs to that user. With SwingBridge, many users run the same application inside a single server JVM, and everything they produce — log statements, stack traces, `System.out` output — lands interleaved in one server log. When a user reports a problem, support needs to follow *that user's* trail through the log. + +SwingBridge solves this with automatic *log attribution*: every application instance gets a short *run ID*, log output is tagged with it, and one *correlation line* per instance ties the run ID to the server session and — once known — the user's name. This page shows how to enable it, how to publish the user identity from either side of the bridge, and what to expect when several logging frameworks coexist in one JVM. + +[[swing-bridge.logging.quick-setup]] +== Quick Setup + +In the Vaadin application (the server that embeds the Swing application), do two things. + +First, start the JVM with console prefixing enabled: + +[source,terminal] +---- +-Dswingbridge.consoleLogPrefix=true +---- + +Second, if the server logs to a file, add the session token to the appender pattern in the logging configuration — for Log4j2: + +[source,xml] +---- + + + ... + +---- + +That's the whole setup. Console output from each Swing application instance is now prefixed with `[swing:]`, file appender lines carry the run ID in the `%X{swingSession}` column, and each application start logs a correlation line: + +[source] +---- +Swing app run 7f3k2a: vaadinSession=57CA0DE6... app=com.example.crm.Main user=alice +... +[swing:7f3k2a] Loading customer list for account 4711 +... +2026-07-13 13:01:41,194 WARN [7f3k2a] [CrmExport] Export failed: disk full +---- + +The support workflow: find the correlation line for the user (or ask the user for the ID, if the application displays it — see <>), then search the log for the run ID. + +[[swing-bridge.logging.concepts]] +== How Attribution Works + +Three concepts appear throughout this page: + +Run ID:: +A short random token (for example, `7f3k2a`) assigned to each Swing application instance — one per user session and application. It stays the same for the lifetime of that instance, so one user's continuing work is one search hit. It's deliberately short and free of personal data, making it safe to show to end users and cheap to repeat on every log line. + +Correlation line:: +One log line per application start: `Swing app run : vaadinSession=... app=... [user=...]`. It joins the run ID to the HTTP session and the user, so the per-line tag can stay minimal. + +Per-thread attribution:: +SwingBridge runs each application instance in an isolated group of threads. Any output produced on those threads — from the Event Dispatch Thread to worker threads the application creates — is attributed to that instance automatically, at the moment of the write. Neither the Swing application nor the Vaadin application does anything to make this work. + +[[swing-bridge.logging.levels]] +== Choosing an Integration Level + +The mechanisms on this page form a ladder, from zero cooperation to full use of the interop APIs. Start at the top; descend only as far as your needs require. Most deployments stop at the second or third rung. + +[cols="1,2,3,3", options="header"] +|=== +|Level |Changes required |What you get |Where on this page + +|0 — automatic +|One JVM flag on the server +|Console output prefixed per user session; correlation lines +|<> + +|1 — configuration only +|One pattern token in the server's logging configuration +|Log files and structured logs tagged per session +|<> + +|2 — one line of code +|One call after login (Swing or Vaadin side) +|The user's name in the correlation lines +|<> + +|3 — the log context API +|Small dependency in the Swing project +|Support ID in the UI, identity reads, incident reporting +|<>, <> + +|4 — interop cooperation +|Annotated Swing methods + generated bridges +|Host-driven initialization of a bespoke logging subsystem +|<> +|=== + +[[swing-bridge.logging.channels]] +== What Gets Tagged, and How + +Log output leaves a server through two different kinds of channels, and each needs its own mechanism. Both are set up in the *Vaadin application*; the Swing application is untouched. + +[[swing-bridge.logging.channels.console]] +=== Console Output + +With `-Dswingbridge.consoleLogPrefix=true`, SwingBridge wraps `System.out` and `System.err`. Every line written by an application-instance thread is prefixed with `[swing:]`; server output passes through unchanged. This catches, regardless of the logging framework the Swing application uses: + +* plain `System.out.println(...)` and `exception.printStackTrace()` — which is how a lot of legacy Swing code reports problems; +* console output of any logging framework, old or new, including multi-line stack traces (each line gets the prefix). + +The flag is off by default because replacing the process streams is observable behavior; enabling it is a deliberate deployment decision. + +[[swing-bridge.logging.channels.files]] +=== Log Files and Structured Logs + +File appenders write straight to disk — they never pass through the console streams, so the prefix above can't reach them. For those, SwingBridge ships logging-framework adapters (inside `swing-bridge-flow`; no extra dependency). They're passive: nothing changes until your pattern references a token. + +*Log4j2* — the common case, since `swing-bridge-flow` itself brings Log4j2. Add the token to any pattern: + +[source,xml] +---- + +---- + +`%X{swingSession}` is filled in automatically: SwingBridge registers a Log4j2 `ContextDataProvider` that captures the run ID at the logging call site, on the calling thread. Because the value travels *with the log event*, it stays correct even with async appenders, where the actual formatting happens on a background thread. Server lines render an empty value. + +Alternatively, `%swingSession` (a pattern converter, discovered automatically) renders the same ID with an explicit `-` for server lines. + +*Logback* — the adapter classes ship with `swing-bridge-flow` and activate when Logback is your backend. Register them in `logback.xml`: + +[source,xml] +---- + + +... +%d %-5level [%swingSession] %logger - %msg%n +---- + +The turbo filter stamps the run ID into the Mapped Diagnostic Context (MDC) at the call site — required for correctness with `AsyncAppender` — after which plain `%X{swingSession}` works, too. + +*`java.util.logging`* — configure the prefixing formatter on a handler: + +[source,properties] +---- +java.util.logging.ConsoleHandler.formatter = com.vaadin.swingbridge.logging.jul.SwingSessionFormatter +---- + +Because the run ID becomes a normal context field, it flows into JSON layouts and log aggregation systems such as Elastic, Loki, and Splunk as a regular attribute: finding everything for run `7f3k2a` becomes a filter instead of a text search. + +[[swing-bridge.logging.identity]] +== Publishing the User Identity + +Attribution tags tell log lines apart; the *identity* ties a run ID to a person. Where the identity call goes depends on where authentication happens. The rule in one line: *whoever learns the user's name calls the method on their side of the bridge.* Everything downstream — correlation lines, prefixes, file tags — is identical. + +[[swing-bridge.logging.identity.swing-side]] +=== Login Inside the Swing Application + +In many migrations, the Swing application keeps its own login dialog, and the Vaadin side has no security context yet. The identity is born inside Swing, so it's published from there. Three options, by how much you can change the Swing code: + +*Option 1 — one line, zero dependencies.* In the *Swing application*, after a successful login, on any thread the application owns (the Event Dispatch Thread is fine): + +[source,java] +---- +System.setProperty("swingbridge.log.user", loggedInUserName); +---- + +Plain JDK API — the Swing project gains no new dependency and behaves identically as a desktop application. SwingBridge routes this property per session (concurrent users can't overwrite each other) and logs a correlation line the moment it's set. + +*Option 2 — the log context API.* If the Swing project already depends on `swing-bridge-annotations` (or accepts it — a small JAR with zero transitive dependencies), the `SwingBridgeLogContext` facade offers more than the property, and every method is desktop-safe: + +[source,java] +---- +// in the login-success handler: +SwingBridgeLogContext.setUser(loggedInUserName); + +// show the support ID in an About or error dialog: +aboutDialog.setSupportId(SwingBridgeLogContext.runId()); + +// "Report a problem" action (see Support Workflow below): +SwingBridgeLogContext.reportIncident("Export failed", exception); +---- + +Standalone on a desktop, the reads return `null`, `setUser()` does nothing, and `reportIncident()` falls back to `System.err` — the same JAR works in both worlds. One packaging note: unlike the pure annotations, these are real code references, so the desktop distribution ships the annotations JAR as well. + +*Option 3 — zero Swing changes.* When the Swing JAR can't be modified at all, the *Vaadin application* observes the Swing application's own session state reflectively and attaches the identity once login completes: + +[source,java] +---- +// In the Vaadin view, after the Swing app has initialized: poll every +// few seconds on a daemon thread until the user has logged in. +SwingBridge.runInAppContext(component, () -> { + ClassLoader appCl = component.getClass().getClassLoader(); + Class settings = Class.forName("com.example.crm.UserSettings", true, appCl); + Field instanceField = settings.getDeclaredField("instance"); + instanceField.setAccessible(true); + Object instance = instanceField.get(null); // observe; never getInstance() + if (instance == null) { + return null; // not logged in yet + } + Object user = settings.getMethod("getCurrentUser").invoke(instance); + return user == null ? null + : (String) user.getClass().getMethod("getName").invoke(user); +}).get(); +// non-blank result -> bridge.setLogIdentity(result); stop polling +---- + +Two rules make this safe: only *read* existing state — never call factory methods such as `getInstance()`, whose constructors may have side effects (server connections, file writes) — and stop polling when the view detaches. + +[[swing-bridge.logging.identity.vaadin-side]] +=== Login Extracted to Vaadin + +The other common shape: authentication and navigation have been modernized into the Vaadin application (for example, with Spring Security), and Vaadin menus open views that each embed a Swing application. Here the identity is born on the server side, so it flows the other way. In each *Vaadin view* that embeds a Swing application: + +[source,java] +---- +SwingBridge bridge = new MyAppBridge(); +bridge.setLogIdentity(authenticatedPrincipal.getName()); +add(bridge); +---- + +Timing is flexible — before adding the bridge to the view or any time later; a correlation line is logged either way. The identity is keyed per _(server session, Swing application)_ pair, which matters when Vaadin handles navigation: views embedding *different* Swing applications each call `setLogIdentity()` for their own bridge (typically factored into a base view class), while two views embedding the *same* application in one session share an instance, so one call covers both. Each opened application gets its own run ID, and all correlation lines carry the same session and user — support can search by application instance (run ID) or by everything the user did (session ID). + +The Swing application needs nothing in this setup — but `SwingBridgeLogContext.runId()` and `reportIncident()` remain fully useful, and `userName()` returns whatever the Vaadin side attached, so the Swing application can display the logged-in user without owning authentication. + +[[swing-bridge.logging.identity.interop]] +=== Bespoke Logging Subsystems + +Some Swing applications route everything through a homegrown logging subsystem that needs the identity *before first use*. Two directions, depending on where the identity lives: + +*Pulling from the Swing side* — the application asks SwingBridge for the values whenever its subsystem initializes: + +[source,java] +---- +// in the Swing application's logging bootstrap: +MyLegacyLogSystem.setGlobalPrefix( + SwingBridgeLogContext.runId() + "/" + SwingBridgeLogContext.userName()); +---- + +*Pushing from the Vaadin side* — when the identity is host-side and the subsystem must be initialized proactively, use the typed interop APIs: annotate a method in the Swing application and call it from the Vaadin view as soon as the application is ready. + +In the *Swing application*: + +[source,java] +---- +@ExposedMethod +public void initLogIdentity(String runId, String userName) { + MyLegacyLogSystem.setGlobalPrefix(runId + "/" + userName); +} +---- + +In the *Vaadin view*, through the generated bridge: + +[source,java] +---- +bridge.interop().of(MyAppBridge.class).onReady(app -> + app.initLogIdentity(runId, principal.getName())); +---- + +The `onReady` callback guarantees the call runs once the application is initialized, before the user interacts with it. See <> for setting up the annotation processing and generated bridges. + +[[swing-bridge.logging.identity.both]] +=== Both Sides + +Setting the identity from both sides is allowed and occasionally useful — for example, Vaadin attaches the single-sign-on principal when the view opens, and the Swing application later refines it after a role or mandate selection. The last write wins for that application instance, and each write logs its own correlation line, so the log history shows the transition rather than losing it. A `null` or blank value removes the identity. + +[[swing-bridge.logging.support-workflow]] +== Support Workflow + +The pieces above combine into a concrete "user reports a bug" flow: + +. The Swing application shows its run ID in an About box or error dialog — `SwingBridgeLogContext.runId()`. The ID is short, random, and contains no personal data. +. When something breaks, the user (or the application's error handler) reports it: ++ +[source,java] +---- +SwingBridgeLogContext.reportIncident("Export to PDF failed", exception); +---- ++ +This writes one warning-level (`WARN`) line to the server log with the full attribution inline, plus the stack trace: ++ +[source] +---- +WARN [7f3k2a] Swing app incident (run=7f3k2a, session=..., user=alice): Export to PDF failed +java.lang.IllegalStateException: disk full + at com.example.crm.export.PdfExporter... +---- +. Support asks the user for the ID from the dialog — or finds the incident line — and searches the server log for `7f3k2a`. Everything that instance logged, before and after the incident, is on tagged lines. + +[[swing-bridge.logging.frameworks]] +== Old and New Logging Frameworks Together + +A migrated deployment typically stacks several logging generations in one JVM: the Swing application may use Log4j 1.x from 2008, the Vaadin application logs through SLF4J, and Spring Boot brings its own opinions. This section describes what actually happens — and the two pitfalls worth knowing. + +[[swing-bridge.logging.frameworks.routing]] +=== Where a Swing Application's Log Statements Go + +The Swing application's JARs are loaded by a class loader that delegates to the server's classpath *first*. Consequences: + +* *Bundled frameworks are shadowed.* If the Swing application ships its own `log4j-core` (or Logback) inside its JARs, those copies are ignored whenever the server classpath provides the same classes — which it does for Log4j2, since `swing-bridge-flow` depends on it. The application's log statements execute against the *server's* Log4j2. +* *Old APIs are bridged.* Code written against Log4j 1.x (`org.apache.log4j.Logger`) works through the `log4j-1.2-api` bridge and ends up in the same place. A 2008-era Swing codebase needs no logging changes. +* *The application's own logging configuration is inert.* Because the log statements run against the server's Log4j2 context, the `log4j2.xml` (or `log4j.properties`) bundled inside the Swing JAR — including its file appenders — never activates. If support is used to finding the application's own log file (say, `~/.myapp/log/client.log`), that file doesn't exist in the SwingBridge deployment: *the same lines are in the server's log instead, attributed per user.* This is the intended outcome, but it's a workflow change worth communicating. + +The practical upshot: tag the *server's* appender patterns (see <>), and every framework generation inside the Swing application is covered. + +In the rare setup where the server deliberately excludes Log4j2 and the Swing application runs its own logging context, the adapters still work — reference them from the *application's* configuration file instead, or add the forwarding appender to stream its events into the server log, attributed: + +[source,xml] +---- + + +---- + +In own-context setups, also give each session its own log file path (for example, include the run ID in the file pattern) — many sessions rolling one shared file is a corruption risk regardless of attribution. + +[[swing-bridge.logging.frameworks.slf4j]] +=== SLF4J, Spring Boot, and Provider Selection + +SLF4J is a facade: at startup it picks one *provider* (binding) that decides where all SLF4J log statements go. Two pitfalls in SwingBridge deployments: + +*Spring Boot defaults to Logback, SwingBridge brings Log4j2.* Running both backends splits your logs in two. The <> template resolves the clash in Logback's favor: it excludes `log4j-slf4j2-impl` from `swing-bridge-flow`, so SLF4J traffic (Spring, SwingBridge itself) goes to Logback. That setup works with the <> — with one caveat: a Swing application calling Log4j APIs directly still logs through Log4j2's own default configuration, which writes to the console only (prefixed, but absent from your Logback-managed files). + +To get *everything* — Spring, SwingBridge, and every Swing application — into one set of tagged appenders, standardize on Log4j2 instead: keep `swing-bridge-flow`'s binding (remove the template's exclusion), and exclude Logback in the Vaadin application's `pom.xml`: + +[source,xml] +---- + + org.springframework.boot + spring-boot-starter-validation + + + ch.qos.logback + logback-classic + + + + + org.apache.logging.log4j + log4j-slf4j2-impl + +---- + +and configure Log4j2 via `log4j2-spring.xml` on the classpath. + +*Multiple providers on the classpath.* SLF4J warns (`Class path contains multiple SLF4J providers`) and then silently picks one — not necessarily the one you want. Notably, Vaadin's development tooling ships a shaded JAR embedding `slf4j-simple`, which can win the auto-selection: the symptom is that SLF4J-routed lines (Spring, SwingBridge's own correlation lines) never reach your Log4j2 appenders, while direct Log4j calls from the Swing application do. The fix is one JVM property that pins the provider explicitly: + +[source,terminal] +---- +-Dslf4j.provider=org.apache.logging.slf4j.SLF4JServiceProvider +---- + +[[swing-bridge.logging.frameworks.matrix]] +=== Coverage Expectations + +[cols="3,2,3", options="header"] +|=== +|How the Swing application logs today |Console (`consoleLogPrefix`) |Log files (pattern token) + +|`System.out.println` / `printStackTrace()` +|Prefixed +|Not applicable — raw prints never reach file appenders + +|Log4j 1.x API (via bridge) +|Prefixed +|Tagged + +|Log4j2 / SLF4J / `java.util.logging` +|Prefixed +|Tagged + +|Bundled framework, own context (server excludes Log4j2) +|Prefixed (console appenders) +|Tag in the application's own configuration, or use the forwarding appender + +|Homegrown logging subsystem +|Raw prints prefixed +|Feed `SwingBridgeLogContext.runId()` / `userName()` into it +|=== + +One structural limit applies everywhere: output produced on *shared* JVM threads that don't belong to any application instance — for example, `ForkJoinPool.commonPool()` — can't be attributed to a user. + +[[swing-bridge.logging.desktop]] +== Desktop Builds Are Unaffected + +Every mechanism on this page is safe outside SwingBridge. The system property in Option 1 is an unused property on the desktop; `SwingBridgeLogContext` reads return `null`, `setUser()` is a no-op, and `reportIncident()` prints to `System.err`; the pattern tokens live in the server's configuration, which desktop builds never see. One Swing codebase serves both distributions. + +[[swing-bridge.logging.troubleshooting]] +== Troubleshooting + +No `[swing:...]` prefixes on the console:: +Check that the JVM was started with `-Dswingbridge.consoleLogPrefix=true`. The flag is off by default. + +`%X{swingSession}` stays empty on lines you expected to be tagged:: +The line was probably logged on a server thread (correlation lines, HTTP request handling) — those are intentionally untagged. If *application* lines stay empty, verify the deployment runs Log4j2 2.13.2 or later; on older versions use `%swingSession` with synchronous appenders. + +Correlation lines and Spring log lines missing from the log file:: +SLF4J is bound to the wrong provider. Look for the `multiple SLF4J providers` warning at startup and pin the provider as shown <>. + +`user=` never appears:: +The identity call never ran. If login is inside the Swing application, confirm the call happens on an application thread (the Event Dispatch Thread qualifies; a shared server executor does not). If Vaadin owns login, confirm each embedding view calls `setLogIdentity()`. + +The Swing application's own log file is missing:: +Expected — see <>. The lines are in the server's log, attributed. From 5b15173a888dda2ea1380e57a95abc3a9b6afda7 Mon Sep 17 00:00:00 2001 From: taefi Date: Mon, 13 Jul 2026 15:57:50 +0300 Subject: [PATCH 2/4] docs: retitle to convey identifying logs per user, not user identification 'User Identification' reads like authentication; the page is about attributing log output to the user who produced it. Renames the page title, SEO title, heading, and the cross-reference labels in configuration.adoc and installation-from-scratch.adoc. --- .../modernization-toolkit/swing-bridge/configuration.adoc | 2 +- .../swing-bridge/installation-from-scratch.adoc | 4 ++-- .../tools/modernization-toolkit/swing-bridge/logging.adoc | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc b/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc index 2f87cc6817..56571e4989 100644 --- a/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc +++ b/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc @@ -47,7 +47,7 @@ This page lists the runtime knobs SwingBridge reads from JVM system properties, |`swingbridge.consoleLogPrefix` |`false` -|When set to `true`, every console line written by an embedded Swing application is prefixed with that instance's run ID (`[swing:]`), so concurrent users' output can be told apart in the server console. See <>. +|When set to `true`, every console line written by an embedded Swing application is prefixed with that instance's run ID (`[swing:]`), so concurrent users' output can be told apart in the server console. See <>. |`swingbridge.log.user` |— diff --git a/articles/tools/modernization-toolkit/swing-bridge/installation-from-scratch.adoc b/articles/tools/modernization-toolkit/swing-bridge/installation-from-scratch.adoc index 77e5d30a4d..0fb23f2603 100644 --- a/articles/tools/modernization-toolkit/swing-bridge/installation-from-scratch.adoc +++ b/articles/tools/modernization-toolkit/swing-bridge/installation-from-scratch.adoc @@ -121,7 +121,7 @@ Add the following parent section, properties, and dependencies to your `pom.xml` ${swing-bridge.version} com.vaadin @@ -151,7 +151,7 @@ Add the following parent section, properties, and dependencies to your `pom.xml` ---- -The `log4j-slf4j2-impl` exclusion keeps Spring Boot's default Logback as the logging backend. See <> for the trade-offs, and for standardizing on Log4j2 instead so that embedded Swing applications' direct Log4j calls end up in the same log files. +The `log4j-slf4j2-impl` exclusion keeps Spring Boot's default Logback as the logging backend. See <> for the trade-offs, and for standardizing on Log4j2 instead so that embedded Swing applications' direct Log4j calls end up in the same log files. SwingBridge requires certain JVM flags so that Maven can access internal Java modules during compilation. Create a `.mvn/jvm.config` file in the project root with the following content: diff --git a/articles/tools/modernization-toolkit/swing-bridge/logging.adoc b/articles/tools/modernization-toolkit/swing-bridge/logging.adoc index 09fe110e76..fb026ecc23 100644 --- a/articles/tools/modernization-toolkit/swing-bridge/logging.adoc +++ b/articles/tools/modernization-toolkit/swing-bridge/logging.adoc @@ -1,6 +1,6 @@ --- -title: Logging pass:[&] User Identification -page-title: Identify users in SwingBridge logs | Vaadin Tools +title: Logging pass:[&] Identifying Logs per User +page-title: Identify logs per user in SwingBridge | Vaadin Tools description: Attribute server log output to the Swing application user who produced it, and integrate old and new logging frameworks. meta-description: Tag SwingBridge log output per user session, publish the logged-in user from Swing or Vaadin, and integrate Log4j, SLF4J, and Spring Boot logging. order: 5 @@ -9,7 +9,7 @@ order: 5 include::{articles}/_vaadin-version.adoc[] [[swing-bridge.logging]] -= Logging & User Identification += Logging & Identifying Logs per User On the desktop, one Swing application serves one user, so its log file belongs to that user. With SwingBridge, many users run the same application inside a single server JVM, and everything they produce — log statements, stack traces, `System.out` output — lands interleaved in one server log. When a user reports a problem, support needs to follow *that user's* trail through the log. From bf084ff09a741fb15226d7ca198bc80c61d12624 Mon Sep 17 00:00:00 2001 From: taefi Date: Fri, 17 Jul 2026 14:55:31 +0300 Subject: [PATCH 3/4] docs: make logging guide newcomer-friendly and document per-line user attribution Restructure the SwingBridge logging page so someone who knows Swing and core Java, but is new to Vaadin/SwingBridge, can get from nothing to tagged logs quickly: - Turn "Quick Setup" into an explicit Step 1 / Step 2 / Step 3 path, leading with the single JVM flag that needs no Swing-app changes. - Retitle the identity section to the task ("Adding the User's Name to the Logs") and lead with the one-line, no-dependency option (System.setProperty("swingbridge.log.user", name) after login); give the who-publishes subsections clearer, task-oriented headings. - Document the new opt-in, swingbridge.includeUserInLogs, which puts the user's name on every line ([swing:|]) and via the %swingUser / %X{swingUser} tokens; note it is off by default because the name is personal data. Add the Log4j2/Logback/JUL token usage and a configuration.adoc row and a troubleshooting entry. --- .../swing-bridge/configuration.adoc | 6 +- .../swing-bridge/logging.adoc | 113 ++++++++++++------ 2 files changed, 84 insertions(+), 35 deletions(-) diff --git a/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc b/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc index 56571e4989..01ddd32990 100644 --- a/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc +++ b/articles/tools/modernization-toolkit/swing-bridge/configuration.adoc @@ -49,9 +49,13 @@ This page lists the runtime knobs SwingBridge reads from JVM system properties, |`false` |When set to `true`, every console line written by an embedded Swing application is prefixed with that instance's run ID (`[swing:]`), so concurrent users' output can be told apart in the server console. See <>. +|`swingbridge.includeUserInLogs` +|`false` +|When set to `true`, the logged-in user's name is added to every attributed log line across all channels — the console prefix becomes `[swing:\|]` and the `%swingUser` / `%X{swingUser}` pattern tokens render the name. Off by default because a user's name is personal data; without it, the name appears only in the per-instance correlation line. Requires an identity to have been published. See <>. + |`swingbridge.log.user` |— -|Not set by the operator: an embedded Swing application sets this property at runtime, after its own login, to publish the authenticated user's name for log attribution. The value is routed per session, so concurrent users can't overwrite each other. See <>. +|Not set by the operator: an embedded Swing application sets this property at runtime, after its own login, to publish the authenticated user's name for log attribution. The value is routed per session, so concurrent users can't overwrite each other. See <>. |=== For Spring Boot, set system properties through `` on the `spring-boot-maven-plugin` (see <>) or pass `-D` flags on the command line. For a packaged JAR, pass them with `-D` on the command line: diff --git a/articles/tools/modernization-toolkit/swing-bridge/logging.adoc b/articles/tools/modernization-toolkit/swing-bridge/logging.adoc index fb026ecc23..6ef587342d 100644 --- a/articles/tools/modernization-toolkit/swing-bridge/logging.adoc +++ b/articles/tools/modernization-toolkit/swing-bridge/logging.adoc @@ -13,43 +13,50 @@ include::{articles}/_vaadin-version.adoc[] On the desktop, one Swing application serves one user, so its log file belongs to that user. With SwingBridge, many users run the same application inside a single server JVM, and everything they produce — log statements, stack traces, `System.out` output — lands interleaved in one server log. When a user reports a problem, support needs to follow *that user's* trail through the log. -SwingBridge solves this with automatic *log attribution*: every application instance gets a short *run ID*, log output is tagged with it, and one *correlation line* per instance ties the run ID to the server session and — once known — the user's name. This page shows how to enable it, how to publish the user identity from either side of the bridge, and what to expect when several logging frameworks coexist in one JVM. +SwingBridge solves this with automatic *log attribution*: every application instance gets a short *run ID*, and each log line it produces is tagged with that ID. Turning it on takes one JVM flag on the server — *no changes to the Swing application*. From there you can, step by step, tag your log files too, add the user's name, and reach a full "user reports a bug" support workflow. + +If you are new to SwingBridge, read the next section top to bottom: it takes you from nothing to tagged logs in two steps, then points to the optional extras. [[swing-bridge.logging.quick-setup]] == Quick Setup -In the Vaadin application (the server that embeds the Swing application), do two things. +Everything in this section is configured in the *Vaadin application* — the server that embeds your Swing application. The Swing code needs no changes to get started. -First, start the JVM with console prefixing enabled: +*Step 1 — Tag console output.* Start the server's JVM with this flag: [source,terminal] ---- -Dswingbridge.consoleLogPrefix=true ---- -Second, if the server logs to a file, add the session token to the appender pattern in the logging configuration — for Log4j2: +Every line an embedded Swing application writes to `System.out` or `System.err` — including `exception.printStackTrace()` and the console output of whatever logging framework it uses — is now prefixed with its run ID: + +[source] +---- +[swing:7f3k2a] Loading customer list for account 4711 +---- + +Server output, and lines from other users, keep their own (different) prefixes, so concurrent users can be told apart at a glance. + +*Step 2 (optional) — Tag log files.* Console prefixing can't reach lines written straight to a log file. If your server logs to a file, add one token to its appender pattern. For Log4j2: [source,xml] ---- - - - ... - + ---- -That's the whole setup. Console output from each Swing application instance is now prefixed with `[swing:]`, file appender lines carry the run ID in the `%X{swingSession}` column, and each application start logs a correlation line: +File lines now carry the run ID in the `%X{swingSession}` column. Other frameworks are covered too — see <>. + +At each application start, SwingBridge also logs one *correlation line* that ties the run ID to the session and (once known) the user: [source] ---- Swing app run 7f3k2a: vaadinSession=57CA0DE6... app=com.example.crm.Main user=alice -... -[swing:7f3k2a] Loading customer list for account 4711 -... -2026-07-13 13:01:41,194 WARN [7f3k2a] [CrmExport] Export failed: disk full ---- -The support workflow: find the correlation line for the user (or ask the user for the ID, if the application displays it — see <>), then search the log for the run ID. +The support workflow is then: find the correlation line for the user (or ask the user for the ID, if the application displays it), and search the log for that run ID. + +*Step 3 (optional) — Show the user's name on every line.* By default the per-line tag is just the run ID; the user's name appears only in the correlation line. To put the name on *every* line as well, see <> — it takes one more flag plus a single line of code (often in the Swing application, sometimes in Vaadin). [[swing-bridge.logging.concepts]] == How Attribution Works @@ -60,7 +67,7 @@ Run ID:: A short random token (for example, `7f3k2a`) assigned to each Swing application instance — one per user session and application. It stays the same for the lifetime of that instance, so one user's continuing work is one search hit. It's deliberately short and free of personal data, making it safe to show to end users and cheap to repeat on every log line. Correlation line:: -One log line per application start: `Swing app run : vaadinSession=... app=... [user=...]`. It joins the run ID to the HTTP session and the user, so the per-line tag can stay minimal. +One log line per application start: `Swing app run : vaadinSession=... app=... [user=...]`. It joins the run ID to the HTTP session and the user, so the per-line tag can stay minimal — just the run ID, unless you opt in to per-line user names (Step 3 above). Per-thread attribution:: SwingBridge runs each application instance in an isolated group of threads. Any output produced on those threads — from the Event Dispatch Thread to worker threads the application creates — is attributed to that instance automatically, at the moment of the write. Neither the Swing application nor the Vaadin application does anything to make this work. @@ -85,14 +92,14 @@ The mechanisms on this page form a ladder, from zero cooperation to full use of |<> |2 — one line of code -|One call after login (Swing or Vaadin side) -|The user's name in the correlation lines -|<> +|One call after login (Swing or Vaadin side), plus an optional flag +|The user's name in the correlation lines — and, with the flag, on every line +|<> |3 — the log context API |Small dependency in the Swing project |Support ID in the UI, identity reads, incident reporting -|<>, <> +|<>, <> |4 — interop cooperation |Annotated Swing methods + generated bridges @@ -115,6 +122,8 @@ With `-Dswingbridge.consoleLogPrefix=true`, SwingBridge wraps `System.out` and ` The flag is off by default because replacing the process streams is observable behavior; enabling it is a deliberate deployment decision. +When you also opt in to per-line user names (see <>), the prefix carries the user as well, for example `[swing:7f3k2a|alice]`. + [[swing-bridge.logging.channels.files]] === Log Files and Structured Logs @@ -131,6 +140,13 @@ File appenders write straight to disk — they never pass through the console st Alternatively, `%swingSession` (a pattern converter, discovered automatically) renders the same ID with an explicit `-` for server lines. +To include the user's name, add the parallel `%X{swingUser}` (or `%swingUser`) token and turn on <>. Without that flag the token renders empty (or `-`), so it's safe to leave in a shared pattern: + +[source,xml] +---- + +---- + *Logback* — the adapter classes ship with `swing-bridge-flow` and activate when Logback is your backend. Register them in `logback.xml`: [source,xml] @@ -138,11 +154,13 @@ Alternatively, `%swingSession` (a pattern converter, discovered automatically) r + ... -%d %-5level [%swingSession] %logger - %msg%n +%d %-5level [%swingSession] [%swingUser] %logger - %msg%n ---- -The turbo filter stamps the run ID into the Mapped Diagnostic Context (MDC) at the call site — required for correctness with `AsyncAppender` — after which plain `%X{swingSession}` works, too. +The turbo filter stamps the run ID (and, when per-line user names are on, the user) into the Mapped Diagnostic Context (MDC) at the call site — required for correctness with `AsyncAppender` — after which plain `%X{swingSession}` and `%X{swingUser}` work, too. Drop the `swingUser` rule and token if you only want the run ID. *`java.util.logging`* — configure the prefixing formatter on a handler: @@ -151,28 +169,52 @@ The turbo filter stamps the run ID into the Mapped Diagnostic Context (MDC) at t java.util.logging.ConsoleHandler.formatter = com.vaadin.swingbridge.logging.jul.SwingSessionFormatter ---- -Because the run ID becomes a normal context field, it flows into JSON layouts and log aggregation systems such as Elastic, Loki, and Splunk as a regular attribute: finding everything for run `7f3k2a` becomes a filter instead of a text search. +The formatter prefixes each record with `[swing:]`, and with per-line user names on it becomes `[swing:|]` — there's no separate token to add. + +Because the run ID (and user) become normal context fields, they flow into JSON layouts and log aggregation systems such as Elastic, Loki, and Splunk as regular attributes: finding everything for run `7f3k2a`, or everything for `alice`, becomes a filter instead of a text search. [[swing-bridge.logging.identity]] -== Publishing the User Identity +== Adding the User's Name to the Logs + +Attribution tags tell log lines apart by run ID; adding the *user's name* ties that ID to a person. There are two parts to it: + +. *Publish the name once.* Someone has to tell SwingBridge who the user is. The rule in one line: *whoever learns the user's name calls the method on their side of the bridge.* Where that call goes depends on where login happens — covered in the subsections below. Once published, the name appears in the correlation line. +. *Choose where it shows.* By default the name stays in the correlation line only, keeping per-line tags short and free of personal data. Opt in, and it appears on every line as well. + +Start with whichever "publish" subsection matches your application; the simplest is a single line in the Swing app. -Attribution tags tell log lines apart; the *identity* ties a run ID to a person. Where the identity call goes depends on where authentication happens. The rule in one line: *whoever learns the user's name calls the method on their side of the bridge.* Everything downstream — correlation lines, prefixes, file tags — is identical. +[[swing-bridge.logging.identity.per-line]] +=== Showing the Name on Every Line + +By default, only the run ID is repeated on each line and the user's name lives in the correlation line. To repeat the name on every line as well, start the server with: + +[source,terminal] +---- +-Dswingbridge.includeUserInLogs=true +---- + +Once an identity has been published, console prefixes then read `[swing:7f3k2a|alice]`, and the `%swingUser` / `%X{swingUser}` tokens (see <>) render the name. Lines logged before login — or on any session with no identity yet — stay `[swing:7f3k2a]`. + +[NOTE] +==== +This flag is off by default because a user's name is personal data, and repeating it on every line increases what your logs contain and retain. It's a single switch that governs the name across *all* channels (console prefix and every framework adapter): with it off, the name is emitted nowhere but the correlation line. +==== [[swing-bridge.logging.identity.swing-side]] -=== Login Inside the Swing Application +=== When Login Happens in the Swing Application -In many migrations, the Swing application keeps its own login dialog, and the Vaadin side has no security context yet. The identity is born inside Swing, so it's published from there. Three options, by how much you can change the Swing code: +In many migrations, the Swing application keeps its own login dialog, and the Vaadin side has no security context yet. The identity is born inside Swing, so it's published from there. Three options, ordered by how much you can change the Swing code — the first is a one-liner and needs no new dependency. -*Option 1 — one line, zero dependencies.* In the *Swing application*, after a successful login, on any thread the application owns (the Event Dispatch Thread is fine): +*Option 1 — one line, no new dependency.* In the *Swing application*, after a successful login, on any thread the application owns (the Event Dispatch Thread is fine): [source,java] ---- System.setProperty("swingbridge.log.user", loggedInUserName); ---- -Plain JDK API — the Swing project gains no new dependency and behaves identically as a desktop application. SwingBridge routes this property per session (concurrent users can't overwrite each other) and logs a correlation line the moment it's set. +This is plain JDK API — the Swing project gains no dependency on SwingBridge and behaves identically as a desktop application (where the property is simply unused). SwingBridge routes the value per session, so concurrent users can't overwrite each other, and logs a correlation line the moment it's set. This is the recommended starting point. -*Option 2 — the log context API.* If the Swing project already depends on `swing-bridge-annotations` (or accepts it — a small JAR with zero transitive dependencies), the `SwingBridgeLogContext` facade offers more than the property, and every method is desktop-safe: +*Option 2 — the log context API (more capabilities).* If the Swing project already depends on `swing-bridge-annotations` (or accepts it — a small JAR with zero transitive dependencies), the `SwingBridgeLogContext` facade offers more than the property, and every method is desktop-safe: [source,java] ---- @@ -213,7 +255,7 @@ SwingBridge.runInAppContext(component, () -> { Two rules make this safe: only *read* existing state — never call factory methods such as `getInstance()`, whose constructors may have side effects (server connections, file writes) — and stop polling when the view detaches. [[swing-bridge.logging.identity.vaadin-side]] -=== Login Extracted to Vaadin +=== When Login Happens in Vaadin The other common shape: authentication and navigation have been modernized into the Vaadin application (for example, with Spring Security), and Vaadin menus open views that each embed a Swing application. Here the identity is born on the server side, so it flows the other way. In each *Vaadin view* that embeds a Swing application: @@ -265,7 +307,7 @@ bridge.interop().of(MyAppBridge.class).onReady(app -> The `onReady` callback guarantees the call runs once the application is initialized, before the user interacts with it. See <> for setting up the annotation processing and generated bridges. [[swing-bridge.logging.identity.both]] -=== Both Sides +=== Setting the Name from Both Sides Setting the identity from both sides is allowed and occasionally useful — for example, Vaadin attaches the single-sign-on principal when the view opens, and the Swing application later refines it after a role or mandate selection. The last write wins for that application instance, and each write logs its own correlation line, so the log history shows the transition rather than losing it. A `null` or blank value removes the identity. @@ -387,7 +429,7 @@ One structural limit applies everywhere: output produced on *shared* JVM threads [[swing-bridge.logging.desktop]] == Desktop Builds Are Unaffected -Every mechanism on this page is safe outside SwingBridge. The system property in Option 1 is an unused property on the desktop; `SwingBridgeLogContext` reads return `null`, `setUser()` is a no-op, and `reportIncident()` prints to `System.err`; the pattern tokens live in the server's configuration, which desktop builds never see. One Swing codebase serves both distributions. +Every mechanism on this page is safe outside SwingBridge. The system properties (`consoleLogPrefix`, `includeUserInLogs`, and the `swingbridge.log.user` value in Option 1) are unused properties on the desktop; `SwingBridgeLogContext` reads return `null`, `setUser()` is a no-op, and `reportIncident()` prints to `System.err`; the pattern tokens live in the server's configuration, which desktop builds never see. One Swing codebase serves both distributions. [[swing-bridge.logging.troubleshooting]] == Troubleshooting @@ -398,10 +440,13 @@ Check that the JVM was started with `-Dswingbridge.consoleLogPrefix=true`. The f `%X{swingSession}` stays empty on lines you expected to be tagged:: The line was probably logged on a server thread (correlation lines, HTTP request handling) — those are intentionally untagged. If *application* lines stay empty, verify the deployment runs Log4j2 2.13.2 or later; on older versions use `%swingSession` with synchronous appenders. +The user's name isn't on each line (only in the correlation line):: +Per-line user names are opt-in. Start the server with `-Dswingbridge.includeUserInLogs=true` (it's off by default because the name is personal data), and make sure an identity has been published — see <>. + Correlation lines and Spring log lines missing from the log file:: SLF4J is bound to the wrong provider. Look for the `multiple SLF4J providers` warning at startup and pin the provider as shown <>. -`user=` never appears:: +`user=` never appears in the correlation line:: The identity call never ran. If login is inside the Swing application, confirm the call happens on an application thread (the Event Dispatch Thread qualifies; a shared server executor does not). If Vaadin owns login, confirm each embedding view calls `setLogIdentity()`. The Swing application's own log file is missing:: From c792c4057b85b8c7928eaaf185c8102058105058 Mon Sep 17 00:00:00 2001 From: taefi Date: Fri, 17 Jul 2026 17:24:01 +0300 Subject: [PATCH 4/4] docs: add on-page TOC and a "since 1.3" note to the logging guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The logging page is long; add an "on this page" table of contents so readers don't lose track. Two complementary aids: - :toc: — the DSP right-rail contents list, auto-built from the section headings (same mechanism as flow/advanced/downloads.adoc). - An "In this article" jump-list near the top linking the nine top-level sections, for in-body navigation. Also mark the feature's availability with a plain-text note under the title ("Available since SwingBridge 1.3."). The DSP since-badge macro has no precedent for a tool version (only Vaadin/Hilla platform coordinates), so a text note states it unambiguously without risking a broken badge. --- .../swing-bridge/logging.adoc | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/articles/tools/modernization-toolkit/swing-bridge/logging.adoc b/articles/tools/modernization-toolkit/swing-bridge/logging.adoc index 6ef587342d..f9d65592ac 100644 --- a/articles/tools/modernization-toolkit/swing-bridge/logging.adoc +++ b/articles/tools/modernization-toolkit/swing-bridge/logging.adoc @@ -10,6 +10,9 @@ include::{articles}/_vaadin-version.adoc[] [[swing-bridge.logging]] = Logging & Identifying Logs per User +:toc: + +_Available since SwingBridge 1.3._ On the desktop, one Swing application serves one user, so its log file belongs to that user. With SwingBridge, many users run the same application inside a single server JVM, and everything they produce — log statements, stack traces, `System.out` output — lands interleaved in one server log. When a user reports a problem, support needs to follow *that user's* trail through the log. @@ -17,6 +20,18 @@ SwingBridge solves this with automatic *log attribution*: every application inst If you are new to SwingBridge, read the next section top to bottom: it takes you from nothing to tagged logs in two steps, then points to the optional extras. +In this article: + +* <> — the two-step path to tagged logs +* <> — run ID, correlation line, per-thread tagging +* <> — how far to go, at a glance +* <> — console vs. log files +* <> — publish the user, show it per line +* <> — the "user reports a bug" flow +* <> — Log4j, SLF4J, Spring Boot +* <> +* <> + [[swing-bridge.logging.quick-setup]] == Quick Setup