cookiejack15 opened a new issue, #4068:
URL: https://github.com/apache/logging-log4j2/issues/4068

   ## Bug Description
   
   `SLF4JLogger.atFatal()` returns `atLevel(Level.TRACE)` instead of 
`atLevel(Level.FATAL)`. This is a copy-paste error introduced in commit 
`113a8e85f4` (LOG4J2-3647, 2023-01-13). Every call to 
`logger.atFatal().log(...)` through the `log4j-to-slf4j` bridge emits the 
message at TRACE level instead of ERROR (SLF4J's equivalent of FATAL). In any 
environment where TRACE is disabled — virtually every production deployment — 
the message is silently discarded.
   
   **Affected file:** 
`log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLogger.java` (line 
366)  
   **Introduced in:** 2.20.0 (commit 113a8e85f4, LOG4J2-3647)  
   **Affected versions:** 2.20.0 through 2.24.3 (current latest stable)  
   **Not affected:** Traditional API `logger.fatal()` works correctly — only 
the fluent `LogBuilder` API is affected.
   
   ## Root Cause
   
   All `at*()` methods delegate to `atLevel()` with the corresponding level 
constant. `atFatal()` was copied from `atTrace()` and the level was never 
updated:
   
   ```java
   public LogBuilder atTrace() {
       return atLevel(Level.TRACE);    // correct
   }
   public LogBuilder atDebug() {
       return atLevel(Level.DEBUG);    // correct
   }
   public LogBuilder atInfo() {
       return atLevel(Level.INFO);     // correct
   }
   public LogBuilder atWarn() {
       return atLevel(Level.WARN);     // correct
   }
   public LogBuilder atError() {
       return atLevel(Level.ERROR);    // correct
   }
   @Override
   public LogBuilder atFatal() {
       return atLevel(Level.TRACE);    // BUG: should be Level.FATAL
   }
   ```
   
   ## What Happens at Runtime
   
   With `Level.TRACE` (the bug), `convertLevel()` returns `TRACE_INT`. Two 
paths lead to message loss:
   
   **Path A — Logback backend (`LAZY_LEVEL_CHECK = true`):**  
   `atFatal()` → `atLevel(Level.TRACE)` → `SLF4JLogBuilder` → `logMessage()` 
passes `Level.TRACE` to SLF4J → Logback checks if TRACE is enabled → disabled 
in production → message silently dropped.
   
   **Path B — Non-Logback SLF4J backend (`LAZY_LEVEL_CHECK = false`):**  
   `atFatal()` → `atLevel(Level.TRACE)` → `AbstractLogger.atLevel()` calls 
`isEnabled(Level.TRACE)` → returns false → returns `LogBuilder.NOOP` → message 
never constructed.
   
   ## Proof of Concept
   
   ```java
   import org.apache.logging.log4j.LogManager;
   import org.apache.logging.log4j.Logger;
   
   // Requires log4j-to-slf4j bridge with SLF4J/Logback backend
   // Logger level set to INFO (standard production configuration)
   public class AtFatalLogLoss {
       private static final Logger logger = 
LogManager.getLogger(AtFatalLogLoss.class);
   
       public static void main(String[] args) {
           // Traditional API: works correctly
           logger.fatal("TRADITIONAL: System crash detected");
           // Output: logged at ERROR level in SLF4J ✓
   
           // Fluent API: message silently lost
           logger.atFatal().log("FLUENT: System crash detected");
           // Output: NOTHING — logged at TRACE, filtered out ✗
       }
   }
   ```
   
   ## Unit Test
   
   ```java
   import ch.qos.logback.classic.LoggerContext;
   import ch.qos.logback.classic.spi.ILoggingEvent;
   import ch.qos.logback.core.read.ListAppender;
   import org.apache.logging.log4j.LogManager;
   import org.apache.logging.log4j.Logger;
   import org.junit.jupiter.api.Test;
   import org.slf4j.LoggerFactory;
   
   import static org.junit.jupiter.api.Assertions.*;
   
   class SLF4JAtFatalLogLossTest {
   
       @Test
       void atFatal_message_is_silently_lost_in_production_config() {
           LoggerContext logbackContext = (LoggerContext) 
LoggerFactory.getILoggerFactory();
           ch.qos.logback.classic.Logger logbackLogger = 
logbackContext.getLogger("test.AtFatal");
           logbackLogger.setLevel(ch.qos.logback.classic.Level.INFO);
   
           ListAppender<ILoggingEvent> appender = new ListAppender<>();
           appender.start();
           logbackLogger.addAppender(appender);
   
           Logger log4jLogger = LogManager.getLogger("test.AtFatal");
   
           // Traditional API: message logged correctly as ERROR
           log4jLogger.fatal("traditional fatal");
           assertEquals(1, appender.list.size());
           assertEquals(ch.qos.logback.classic.Level.ERROR, 
appender.list.get(0).getLevel());
   
           appender.list.clear();
   
           // Fluent API: message SILENTLY LOST
           log4jLogger.atFatal().log("fluent fatal");
           assertEquals(1, appender.list.size(),
               "atFatal().log() should produce a log event, but the message is 
silently lost");
       }
   
       @Test
       void atFatal_logs_at_trace_level_instead_of_error() {
           LoggerContext logbackContext = (LoggerContext) 
LoggerFactory.getILoggerFactory();
           ch.qos.logback.classic.Logger logbackLogger = 
logbackContext.getLogger("test.AtFatalLevel");
           logbackLogger.setLevel(ch.qos.logback.classic.Level.TRACE);
   
           ListAppender<ILoggingEvent> appender = new ListAppender<>();
           appender.start();
           logbackLogger.addAppender(appender);
   
           Logger log4jLogger = LogManager.getLogger("test.AtFatalLevel");
   
           log4jLogger.atFatal().log("this should be ERROR level");
   
           assertEquals(1, appender.list.size());
           // FAILS: actual level is TRACE, not ERROR
           assertEquals(ch.qos.logback.classic.Level.ERROR, 
appender.list.get(0).getLevel(),
               "atFatal() should map to SLF4J ERROR, but actual level is: " + 
appender.list.get(0).getLevel());
       }
   }
   ```
   
   ## Proposed Fix
   
   One-word fix — change `TRACE` to `FATAL`:
   
   ```diff
    @Override
    public LogBuilder atFatal() {
   -    return atLevel(Level.TRACE);
   +    return atLevel(Level.FATAL);
    }
   ```
   
   ## Affected Versions
   
   | Version | Status |
   |---------|--------|
   | < 2.20.0 | Not affected |
   | 2.20.0 – 2.24.3 | Affected |
   
   The fluent `atFatal()` method in `SLF4JLogger` has been non-functional since 
its introduction in 2.20.0 (7 releases, 3+ years).


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to