Repository navigation
[java] Make selenium.debug/webdriver.verbose configure the real logger (Mechanism Only) - #17841
Conversation
Debug.java now owns a single configureLogger() entry point that installs a level-aware handler on the shared Selenium logger, replacing the old pair of switches that didn't actually agree with each other. LoggingOptions and RemoteWebDriver call into it so Grid and driver sessions both honor the same debug-log level, closing SeleniumHQ#12892. Adds DebugTest, LoggingOptionsTest, RemoteWebDriverInitializationTest, and devtools/ConnectionTest as the direct tests for this mechanism.
…tructed Connections BiDi and CDP Connection instances built without going through RemoteWebDriver or DriverFinder (a caller wiring one up directly) never triggered the debug-log-level configuration, so their FINE wire diagnostics stayed governed by whatever level was set last -- or never set at all. Both constructors now call Debug.configureLogger() first, same idempotent pattern DriverFinder.getBinaryPaths() already uses.
PR Summary by Qodo[java] Make selenium.debug/webdriver.verbose configure the real logger
AI Description
Diagram
High-Level Assessment
Files changed (12)
|
Code Review by Qodo
1.
|
…tors Qodo review findings 1 and 2 on SeleniumHQ#17841: the BiDi and CDP Connection constructors modified to call Debug.configureLogger() had no Javadoc, so API consumers had no way to discover the parameters or the debug- logging side effect. Adds Javadoc mirroring RemoteWebDriver's canonical-constructor doc block in this same branch. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LfxetzdJtF13MCJDRNQwgG
…s' reset exemption to it Fixes Qodo review findings 3-6 on SeleniumHQ#17841: - Debug.configureLogger()'s fast-path used to key solely on shouldDebug == loggerConfigured, so if something outside this class removed the installed handler while debugging stayed on (a JUL LogManager.reset(), or unrelated removeHandler() code), the handler was never reinstalled. The fast-path now also consults isHandlerCurrentlyInstalled(), and the level-raising bookkeeping only runs on a genuine off->on transition so a repair-only call can't corrupt the snapshot turning debug off later needs. The turn-off path also guards against installedHandler already being null, since Logger.removeHandler(null) throws NPE. - LoggingOptions.configureLogging()'s handler-reset loop used to skip org.openqa.selenium unconditionally, preserving any handler attached there, not just the one Debug.configureLogger() installed. Adds Debug.isOwnHandler(Handler) so the loop can strip everything else and keep only Debug's own. - Adds missing Javadoc to configureLogging(). DebugTest and LoggingOptionsTest each gain a regression test exercising the fixed behavior, watched red against the prior code before the fix landed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LfxetzdJtF13MCJDRNQwgG
|
Thanks for the review, @qodo-code-review — addressed all six findings in two new commits ( 1 & 2 — Missing Javadoc on the BiDi/CDP 3 — Missing Javadoc on 4 — 5 — 6 — One note on finding 6's suggested verification approach: the "snapshot handler count before/after" check in the original suggestion doesn't actually catch this bug in this codebase — All four scoped Bazel test targets pass: |
Thanks for the detailed follow-up. The two commits and regression coverage address all six findings:
The identity-based handler assertion is the appropriate verification here, since a count-only assertion would be masked by |
|
Code review by qodo was updated up to the latest commit 4296a51 |
…ts own Qodo review finding on SeleniumHQ#17841 (comment 3672251850): configureLogger() treated a null own level on org.openqa.selenium as always needing a raise to FINE, even when a parent logger already had an explicit, more-verbose level (e.g. FINER) -- silently clobbering inherited verbosity down to FINE. Adds a small effectiveLevel() helper that walks the logger's parent chain to the first non-null level (falling back to Level.INFO), and uses that instead of the logger's own level to decide whether a raise is needed. What gets snapshotted/restored on turn-off is still the logger's own (possibly null) level, unchanged. Fixing this surfaced a real order-dependent brittleness in LoggingOptionsTest#configureLoggingRaisesSeleniumLoggerEvenWithExternalJulConfigSet: it asserted the logger's own explicit level was FINE, which no longer holds once an earlier test in the same JVM already left an ancestor logger (the root logger, via this same configureLogging() path) at FINE -- correctly, there's nothing left to raise. Updated the assertion to check effective loggability, which is what the test was actually meant to prove. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LfxetzdJtF13MCJDRNQwgG
|
Code review by qodo was updated up to the latest commit 072512c |
…he inherited-level test Qodo review finding on SeleniumHQ#17841 (comment 3672369609): configureLoggerDoesNotClampAnInheritedMoreVerboseEffectiveLevel asserted org.openqa.selenium's own level was null as a starting-state precondition but never enforced it -- if an earlier test in the same JVM run had left an explicit (non-null) level set, this test would fail on that precondition before ever exercising the behavior under test. @AfterEach already restores the logger's own level after every test, so forcing it to null at the start of this test is safe and cannot leak into others. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LfxetzdJtF13MCJDRNQwgG
|
Code review by qodo was updated up to the latest commit 4d51cec |
|
Code review by qodo was updated up to the latest commit c569d65 |
…er on distinct destinations Qodo review finding on SeleniumHQ#17841 (comment 3672531999, "Grid drops debug logs"): rootHandlerFilter() suppressed org.openqa.selenium FINE/CONFIG records from Grid's root handler whenever no log-file was configured, on the assumption that destination always coincides with Debug's own stderr-only ConsoleHandler. That's only true when Debug.isDebugAll() (SE_DEBUG) is set: getOutputStream() only defaults to System.err in that case. With debugging enabled instead via -Dselenium.debug=true/-Dselenium.webdriver.verbose=true (no SE_DEBUG), getOutputStream() defaults to System.out -- a genuinely different destination from Debug's handler -- so the suppression made Selenium's FINE-level wire diagnostics invisible to a Grid operator watching stdout/structured log output, this PR's own advertised primary use case. Narrows the suppression condition to !logFileConfigured && Debug.isDebugAll(), matching getOutputStream()'s own exact condition for routing to stderr. Investigating this surfaced that the existing regression test (configureLoggingDoesNotDuplicateSeleniumDebugRecordsWhenNoLogFileIsConfigured) only ever set the selenium.debug property, never the SE_DEBUG environment variable its own comment claimed to cover -- it was unknowingly exercising the very branch this fix changes, and would have started failing once the production fix landed. Corrected its setup to genuinely exercise Debug.isDebugAll() by setting SE_DEBUG via an environment-variable stub (uk.org.webcompere:system-stubs, following the existing pattern already used in remote/service/DriverFinderTest.java), rather than leaving a now-contradictory assertion in place. Added a new test proving the fixed scenario -- selenium.debug=true without SE_DEBUG, no log-file -- where the record must now reach Grid's root handler (stdout) as well as Debug's own (stderr), since they are genuinely different destinations there. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LfxetzdJtF13MCJDRNQwgG
|
Code review by qodo was updated up to the latest commit 325168c |
With SE_DEBUG stubbed on and no log-file, BOTH Grid's root handler and Debug's own ConsoleHandler target System.err -- nothing goes to stdout in this scenario at all, so the old `captured.out()` doesNotContain assertion was vacuously true and a real duplication regression (the marker printed twice to stderr, once per handler) would have passed unnoticed. Verified by mutation: with rootHandlerFilter()'s suppression temporarily disabled, the old assertions still passed while containsOnlyOnce correctly failed with "appeared 2 times". The stdout check stays as a secondary sanity check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LfxetzdJtF13MCJDRNQwgG
The @AfterEach cleanup calls Debug.configureLogger() to re-sync Debug with the restored switch state, but that only works for switches restored synchronously inside the method itself (the plain system properties). The stubbed SE_DEBUG is restored by SystemStubsExtension's own lifecycle callback, which runs AFTER user @AfterEach methods complete -- so the re-sync still saw SE_DEBUG=true, kept Debug's handler installed, and the stub's later restoration went unnoticed, leaking the handler (and Debug's loggerConfigured bookkeeping) into later tests in the same JVM. Explicitly setting the stub to "false" first makes configureLogger() see the correct final state and tear the handler down deterministically. Regression test invokes the real cleanup method and was watched fail (handler still installed) before the fix, pass after. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LfxetzdJtF13MCJDRNQwgG
|
Code review by qodo was updated up to the latest commit a503868 |
|
Code review by qodo was updated up to the latest commit 1c44aca |
|
@titusfortner any guidance as to what needs to be updated for this pr to be merged? |
Code Review by Qodo
1.
|
|
@titusfortner thanks for laying this out. My take on each point:
@MohabMohie a few things before this can be merged:
|
The last merge from trunk kept a stale `requireNonNullElseGet` call that
has no import, so `//java/src/org/openqa/selenium/remote` no longer
compiled, and dropped the `WebDriverException` import that trunk's
RemoteWebDriverInitializationTest still uses.
Restore trunk's `Require.nonNull("Capabilities", capabilities)` so a null
argument fails fast again instead of reaching checkNonW3CCapabilities,
and leave the two `Debug.configureLogger()` calls as the only changes to
RemoteWebDriver. Document the constructor that applies the live debug
switches, and cover null capabilities and the live switch in tests.
`configureLogger()` was a `static synchronized` method, which SpotBugs reports as USO_UNSAFE_STATIC_METHOD_SYNCHRONIZATION and fails `//java/src/org/openqa/selenium:core-lib-spotbugs`. Synchronize on a private lock instead. Per review, debug logging is now one-way: the switches are read live on every call, and once one is seen debug logging stays on for the life of the JVM. That removes the level snapshot/restore logic and its edge cases. SE_DEBUG and the system properties now name their own level, with SE_DEBUG checked first, so either can change later without the other. The Javadoc now says that users with their own FINE handler will see FINE records twice. Tests cover the switches, the one-way behaviour, live pickup, idempotence under concurrent calls, user handlers and the root logger, more-verbose levels, repair, and the documented duplicates.
|
Thanks @diemol, @titusfortner. I've pushed two commits addressing the open feedback:
Tests: I've also updated the description with a release note (enabling the switch now shows all Selenium |
|
/agentic_review |
|
Code review by qodo was updated up to the latest commit baa8beb |
Debug logging is one-way for the JVM, so the live-switch test in RemoteWebDriverInitializationTest left FINE logging and a console handler behind for later driver tests, and its first assertion depended on SE_DEBUG being unset. Move it to RemoteWebDriverDebugLoggingTest, which Bazel runs in its own JVM, and stub SE_DEBUG and the debug properties there. RemoteWebDriverInitializationTest is back to trunk apart from the null-capabilities test, which now uses a failing lambda executor instead of a Mockito mock.
Spell out in configureLogger()'s Javadoc that Selenium's debug handler only prints records below INFO, so turning off useParentHandlers on org.openqa.selenium needs a user handler to keep INFO output.
|
Thanks for the review! Pushed two small follow-ups:
The older "logging level is lost" finding no longer applies since the restore logic is gone. Tests pass locally. Could someone approve the workflow runs when you get a chance? |
Related Issues
Part of #12892
Follow-up call-site migrations are tracked in #17835.
What does this PR do?
Makes
-Dselenium.debug=true, the legacy-Dselenium.webdriver.verbose=true,and
SE_DEBUGshow Java JUL diagnostics from theorg.openqa.seleniumlogger.The system properties only affect the Java bindings' JUL output; Grid and
driver-process output stay with
SE_DEBUG.Debug.configureLogger():FINEonly when it is less verbose, never lowering an explicit or inheritedFINER/FINEST/ALL;INFO, without changing user handlers or the root logger (users who also attach their ownFINEhandler will seeFINErecords twice);FINEloggability if something resets them;SE_DEBUGand the properties separate levels (checked in that order) so either can become more verbose later; andRemoteWebDriverapplies the live configuration on each construction while keeping the early class-initialization hook for driver-discovery logs.getDebugLogLevel()is deprecated but keeps its legacy behavior while the remaining callers move to fixed levels in follow-up PRs.Release note: enabling
selenium.debug/selenium.webdriver.verbosenow shows all SeleniumFINEdiagnostics, so output increases for existing users of the switch.BiDi/CDP constructor wiring, Grid changes, and bulk call-site migrations are intentionally outside this mechanism-only PR.
Testing
//java/test/org/openqa/selenium/internal:DebugTest//java/test/org/openqa/selenium/remote:RemoteWebDriverInitializationTest//java/src/org/openqa/selenium:core-lib-spotbugsThe tests cover each switch, live pickup, one-way behaviour, idempotence under concurrent calls, user handler and root logger preservation, explicit and inherited more-verbose levels, repair after external handler/level changes, duplicate
INFOsuppression, the documented duplicateFINEoutput for user handlers, and null-capabilities validation.Types of changes
AI assistance