Skip to content

[java] SE_DEBUG dumps all log output to stderr#16900

Merged
titusfortner merged 4 commits intotrunkfrom
debug_driver
Jan 14, 2026
Merged

[java] SE_DEBUG dumps all log output to stderr#16900
titusfortner merged 4 commits intotrunkfrom
debug_driver

Conversation

@titusfortner
Copy link
Member

@titusfortner titusfortner commented Jan 13, 2026

User description

🔗 Related Issues

Existing Debug.java implementation uses a clever trick to provide additional debugging info via system property by logging things that are normally "FINE" level at "INFO" so they are seen by the user without messing with their other logging setup. (See #12892)

So when I implemented #16816 I did the easy thing ad used it to toggle the clever behavior. Except this usage isn't consistent in the codebase, so it doesn't actually do that much.

💥 What does this PR do?

  • Keeps existing released behavior for the 2 system properties
  • Firehoses all debugging info to stderr when SE_DEBUG is set.
  • Overrides Grid & Driver logging settings to use level FINE/DEBUG sent to stderr
  • Adds a handler for Selenium JUL, so it is additive to user's other logging solutions unless they explicitly remove selenium handlers elsewhere.

🔧 Implementation Notes

  • This is a "firehose to stderr" solution
  • If a user has SE_DEBUG set for Selenium manager this will add a lot of extra output, so could be SE_DEBUG_ALL?

💡 Additional Considerations

Other languages next

🔄 Types of changes

  • New feature (non-breaking change which adds functionality and tests!)

PR Type

Enhancement


Description

  • Adds SE_DEBUG environment variable support for comprehensive debug logging

  • Routes all Selenium debug output to stderr when SE_DEBUG is enabled

  • Overrides driver and grid logging to use FINE/DEBUG level with stderr output

  • Configures JUL handler for Selenium package to capture all debug information


Diagram Walkthrough

flowchart LR
  SE_DEBUG["SE_DEBUG env var"]
  isDebugAll["isDebugAll() check"]
  configLogger["configureLogger()"]
  driverService["Driver Services"]
  gridLogging["Grid Logging"]
  julHandler["JUL Handler to stderr"]
  
  SE_DEBUG --> isDebugAll
  isDebugAll --> configLogger
  configLogger --> julHandler
  isDebugAll --> driverService
  isDebugAll --> gridLogging
  driverService --> julHandler
  gridLogging --> julHandler
Loading

File Walkthrough

Relevant files
Enhancement
Debug.java
Add SE_DEBUG support and logger configuration                       

java/src/org/openqa/selenium/internal/Debug.java

  • Separates SE_DEBUG environment variable check into new isDebugAll()
    method
  • Removes SE_DEBUG from static IS_DEBUG initialization
  • Adds configureLogger() method to set up JUL handler for stderr output
  • Creates StreamHandler with DebugLogFormatter for Selenium package
    logging
+31/-5   
ChromeDriverService.java
Enable verbose logging when SE_DEBUG is set                           

java/src/org/openqa/selenium/chrome/ChromeDriverService.java

  • Imports Debug class for debug mode checking
  • Checks Debug.isDebugAll() to enable verbose logging
  • Simplifies verbose flag setting to use boolean true instead of
    property value
+4/-2     
EdgeDriverService.java
Enable verbose logging when SE_DEBUG is set                           

java/src/org/openqa/selenium/edge/EdgeDriverService.java

  • Imports Debug class for debug mode checking
  • Checks Debug.isDebugAll() to enable verbose logging
  • Simplifies verbose flag setting to use boolean true instead of
    property value
+4/-2     
GeckoDriverService.java
Set Firefox debug level when SE_DEBUG is enabled                 

java/src/org/openqa/selenium/firefox/GeckoDriverService.java

  • Imports Debug class for debug mode checking
  • Sets log level to DEBUG when SE_DEBUG is enabled
  • Refactors log level property parsing to handle null values properly
+6/-5     
LoggingOptions.java
Route grid logging to stderr in debug mode                             

java/src/org/openqa/selenium/grid/log/LoggingOptions.java

  • Imports Debug class for debug mode checking
  • Overrides log level to FINE when SE_DEBUG is enabled
  • Routes output to stderr instead of stdout when SE_DEBUG is active
+5/-1     
RemoteWebDriver.java
Initialize debug logger on RemoteWebDriver load                   

java/src/org/openqa/selenium/remote/RemoteWebDriver.java

  • Adds static initializer block to configure logger on class load
  • Calls Debug.configureLogger() to set up JUL handler for debug output
+4/-0     
DriverService.java
Route driver service output to stderr in debug mode           

java/src/org/openqa/selenium/remote/service/DriverService.java

  • Imports Debug class for debug mode checking
  • Routes driver output to stderr by default when SE_DEBUG is enabled
  • Uses LOG_STDERR as default location instead of LOG_NULL in debug mode
+3/-1     

@selenium-ci selenium-ci added B-grid Everything grid and server related C-java Java Bindings labels Jan 13, 2026
@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 13, 2026

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
Sensitive data in logs

Description: Enabling SE_DEBUG routes Selenium/package logs to System.err at FINE level via a new
handler, which can inadvertently expose sensitive data (e.g., URLs with embedded
credentials, session IDs, capabilities, or auth tokens) to console/CI logs.
Debug.java [48-71]

Referred Code
public static boolean isDebugAll() {
  return Boolean.parseBoolean(System.getenv("SE_DEBUG"));
}

public static void configureLogger() {
  if (!isDebugAll() || loggerConfigured) {
    return;
  }

  Logger logger = Logger.getLogger("org.openqa.selenium");
  logger.setLevel(Level.FINE);

  Handler handler =
      new StreamHandler(System.err, new DebugLogFormatter()) {
        @Override
        public synchronized void publish(java.util.logging.LogRecord record) {
          super.publish(record);
          flush();
        }
      };
  handler.setLevel(Level.FINE);


 ... (clipped 3 lines)
Ticket Compliance
🟡
🎫 #5678
🔴 Investigate and fix repeated ChromeDriver instantiation causing "Error: ConnectFailure
(Connection refused)" messages after the first instance.
Provide guidance or a code change that prevents or mitigates the ConnectFailure console
errors on subsequent driver instantiations (Ubuntu 16.04 / Chrome 65 / ChromeDriver 2.35 /
Selenium 3.9.0).
🟡
🎫 #1234
🔴 Restore behavior where clicking a link with JavaScript in the href triggers the expected
JavaScript (regression from Selenium 2.47.1 to 2.48.x) in Firefox 42.
Provide a fix and/or regression test demonstrating that click() triggers the JavaScript
href alert as expected.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Debug to stderr: Enabling SE_DEBUG configures a global JUL handler that emits FINE Selenium logs to
System.err, which may expose internal details to end-users depending on runtime usage and
log content.

Referred Code
public static void configureLogger() {
  if (!isDebugAll() || loggerConfigured) {
    return;
  }

  Logger logger = Logger.getLogger("org.openqa.selenium");
  logger.setLevel(Level.FINE);

  Handler handler =
      new StreamHandler(System.err, new DebugLogFormatter()) {
        @Override
        public synchronized void publish(java.util.logging.LogRecord record) {
          super.publish(record);
          flush();
        }
      };
  handler.setLevel(Level.FINE);
  logger.addHandler(handler);
  loggerConfigured = true;
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Unstructured stderr logs: When SE_DEBUG is set, logging is forced to FINE and routed to stderr which can increase
risk of sensitive data appearing in unstructured logs depending on what downstream
components log at debug level.

Referred Code
if (Debug.isDebugAll()) {
  configLevel = Level.FINE.getName();
}

try {
  level = Level.parse(configLevel.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
  // Logger is not configured yet
  new ConfigException(
          "Unable to determine log level from "

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Jan 13, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Centralize logging initialization logic

The current logging initialization in a static block in RemoteWebDriver is
unreliable. It should be moved to explicit entry points like DriverService
builders and LoggingOptions to ensure consistent behavior.

Examples:

java/src/org/openqa/selenium/remote/RemoteWebDriver.java [117-119]
  static {
    org.openqa.selenium.internal.Debug.configureLogger();
  }
java/src/org/openqa/selenium/internal/Debug.java [52-71]
  public static void configureLogger() {
    if (!isDebugAll() || loggerConfigured) {
      return;
    }

    Logger logger = Logger.getLogger("org.openqa.selenium");
    logger.setLevel(Level.FINE);

    Handler handler =
        new StreamHandler(System.err, new DebugLogFormatter()) {

 ... (clipped 10 lines)

Solution Walkthrough:

Before:

// In java/src/org/openqa/selenium/remote/RemoteWebDriver.java
public class RemoteWebDriver implements WebDriver, ... {
  static {
    // Logging is configured here, dependent on this class being loaded.
    org.openqa.selenium.internal.Debug.configureLogger();
  }
  // ...
}

// In java/src/org/openqa/selenium/internal/Debug.java
public class Debug {
  private static boolean loggerConfigured = false;
  public static void configureLogger() {
    if (!isDebugAll() || loggerConfigured) {
      return;
    }
    // ... configure logger for "org.openqa.selenium"
    loggerConfigured = true;
  }
}

After:

// In java/src/org/openqa/selenium/remote/RemoteWebDriver.java
public class RemoteWebDriver implements WebDriver, ... {
  // The static initializer block is removed to avoid side-effect-based initialization.
  // ...
}

// In java/src/org/openqa/selenium/remote/service/DriverService.java
public abstract static class Builder<...> {
  protected Builder() {
    // Explicitly configure logger when a DriverService is being built.
    Debug.configureLogger();
    // ...
  }
}

// In java/src/org/openqa/selenium/grid/log/LoggingOptions.java
public class LoggingOptions {
  public LoggingOptions(Config config) {
    // Explicitly configure logger when Grid logging is initialized.
    Debug.configureLogger();
    this.config = config;
  }
}
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a fragile initialization pattern using a static block, which can cause inconsistent behavior, and proposes a more robust, explicit initialization strategy that improves the feature's reliability.

High
Possible issue
Add null check before parsing property

Add a null check for the GECKO_DRIVER_LOG_LEVEL_PROPERTY system property before
passing its value to FirefoxDriverLogLevel.fromString() to prevent a
NullPointerException.

java/src/org/openqa/selenium/firefox/GeckoDriverService.java [237-242]

 if (Debug.isDebugAll()) {
   logLevel = FirefoxDriverLogLevel.DEBUG;
 } else if (logLevel == null) {
-  logLevel =
-      FirefoxDriverLogLevel.fromString(System.getProperty(GECKO_DRIVER_LOG_LEVEL_PROPERTY));
+  String level = System.getProperty(GECKO_DRIVER_LOG_LEVEL_PROPERTY);
+  if (level != null) {
+    logLevel = FirefoxDriverLogLevel.fromString(level);
+  }
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential NullPointerException introduced by the PR, which removed a necessary null check that existed in the previous code. Restoring this check prevents a runtime crash.

Medium
Synchronize method to prevent race conditions

Add the synchronized keyword to the configureLogger method signature to prevent
a race condition where multiple threads could add duplicate log handlers.

java/src/org/openqa/selenium/internal/Debug.java [52-71]

-public static void configureLogger() {
+public static synchronized void configureLogger() {
   if (!isDebugAll() || loggerConfigured) {
     return;
   }
 
   Logger logger = Logger.getLogger("org.openqa.selenium");
   logger.setLevel(Level.FINE);
 
   Handler handler =
       new StreamHandler(System.err, new DebugLogFormatter()) {
         @Override
         public synchronized void publish(java.util.logging.LogRecord record) {
           super.publish(record);
           flush();
         }
       };
   handler.setLevel(Level.FINE);
   logger.addHandler(handler);
   loggerConfigured = true;
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a race condition in the configureLogger method. While the static initializer in RemoteWebDriver makes concurrent execution less likely, it's still possible, and synchronizing the method is a robust fix.

Low
General
Disable parent log handlers

Call logger.setUseParentHandlers(false) in the configureLogger method to prevent
duplicate log entries from parent logger handlers.

java/src/org/openqa/selenium/internal/Debug.java [57-58]

 Logger logger = Logger.getLogger("org.openqa.selenium");
+logger.setUseParentHandlers(false);
 logger.setLevel(Level.FINE);
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: This is a valid improvement to prevent duplicate log messages, which can occur if a parent logger (like the root logger) also has handlers configured. Disabling parent handlers makes the logging configuration more robust and predictable.

Medium
Learned
best practice
Validate and trim env var

Trim and null-check SE_DEBUG before parsing so values like " true " behave as
expected and missing env vars don't rely on implicit parsing behavior.

java/src/org/openqa/selenium/internal/Debug.java [48-50]

 public static boolean isDebugAll() {
-  return Boolean.parseBoolean(System.getenv("SE_DEBUG"));
+  String value = System.getenv("SE_DEBUG");
+  return value != null && Boolean.parseBoolean(value.trim());
 }
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why:
Relevant best practice - Add explicit validation and guards at integration boundaries (environment variables) by trimming/checking presence before use.

Low
  • Update

Copy link
Contributor

@asolntsev asolntsev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to repeat myself:
This specific PR can be merged since it doesn't make things worth.

But generally, it's a wrong approach to enable debug logs in Java.

The right approach is the following:

  1. Java code always logs "debug" message:
    LOG.debug("Here comes some debugging info rarely useful");

  2. Users have configuration file, say, logback.xml - where they can enable or disable debug logs for some specific packages or classes:

<configuration>
    <!-- Console / file / kafka / ... appender -->
    <appender name="CONSOLE" class="..."/>

    <!-- Enable DEBUG only for this package -->
    <logger name="org.openqa.selenium" level="DEBUG"/>

    <!-- Root logger (everything else is logged with INFO by default) -->
    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

@asolntsev asolntsev added this to the 4.40.0 milestone Jan 13, 2026
@titusfortner
Copy link
Member Author

titusfortner commented Jan 13, 2026

It's a feature that Selenium doesn't require slf4j, and doesn't force any particular logging dependencies on users. if they know what they are doing with logging, it's easy enough for them to add the required configs. The problem is how to help them when they don't know what they are doing.

I guess we could implement our internal testing framework code with slf4j to accomplish what I'm suggesting. But I think there is value in having this come from the library directly.

All that being said, I'm not sure we want this toggle to be what we advertise to users. I'm interested in a way for me to see the issues like what is timing out where. The only restriction I have is that if a user toggles it, I don't want it to get in the way of whatever other logging solution(s) they may have.

@asolntsev
Copy link
Contributor

I understand.
Sorry, my example was for Logback, but the similar approach exists for all logging frameworks, including JUL (aka "java.util.logging") used in Selenium.

In JUL, we need to have a file named logging.properties:

# Global default
.level = INFO

# Enable DEBUG-like logging for a specific package
org.openqa.selenium.bidi.level = FINE

# Make sure handlers actually output FINE
java.util.logging.ConsoleHandler.level = FINE
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

@titusfortner
Copy link
Member Author

I guess I'm still trying to understand if you are suggesting a different approach to the functionality I'd like to see. You'd rather we put something in our test framework rather than hard coding it in the library directly?

@titusfortner titusfortner merged commit 1a8200f into trunk Jan 14, 2026
21 of 22 checks passed
@titusfortner titusfortner deleted the debug_driver branch January 14, 2026 18:15
@asolntsev
Copy link
Contributor

I guess I'm still trying to understand if you are suggesting a different approach to the functionality I'd like to see. You'd rather we put something in our test framework rather than hard coding it in the library directly?

Yes, I am suggesting a different approach.

  1. In Java code, there should NOT be any ifs like if (...) log.debug(msg) else log.info(msg).
    Should be only log.debug(msg).
  2. In users code, there should be configured "either I want to see debug logs or not".

But we can do it later, it doesn't stop us from merging this PR.

@titusfortner
Copy link
Member Author

if (...) log.debug(msg) else log.info(msg).

Oh, that, yes, 💯 agree. I'm planning to submit a deprecation for that one and point people to docs (we probably also need to improve those). But that's why this PR completely changed away from what the system properties are doing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B-grid Everything grid and server related C-java Java Bindings Review effort 3/5

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants