SLF4J 2.0.18:Marker 终于不再丢失,WARN 级别也能自定义了

2026-05-13 25 预计阅读时间:1 分钟
来源:oschina.net AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:8 分钟

SLF4J 2.0 系列持续修补细节。2.0.18 修了两个看似不起眼但实际影响使用体验的问题:一是 DefaultLoggingEventBuilder 在调用 LocationAwareLogger 时把 marker 丢成了 null,二是 SimpleLogger 渲染 WARN 级别时硬编码字符串,无法跟随配置变化。这两个修复分别影响"带标记的日志到底能不能传到底层"和"轻量场景下日志输出可定制性",值得升级。

Marker 传递:从 null 到真正生效

SLF4J 2.0 引入了 Fluent API(LoggingEventBuilder),写日志的方式变成了:

logger.atInfo().addMarker(MarkerFactory.getMarker("AUDIT")).log("操作完成");

但问题出在 DefaultLoggingEventBuilder 内部。当底层 logger 是 LocationAwareLogger(Logback 就是典型实现)时,logViaLocationAwareLoggerAPI() 方法负责把事件转发给 locationAwareLogger.log()。这个方法的签名需要接收 marker 参数,而之前的实现直接传了 null——即使你在 Fluent API 里明确加了 marker,到了底层也被抹掉了。

2.0.18 的修复逻辑很直接:从 markerList 中取出第一个 marker 传下去:

// 修复前(伪代码示意)
locationAwareLogger.log(null, fqcn, level, message, argArray, throwable);

// 修复后
Marker firstMarker = markerList.isEmpty() ? null : markerList.get(0);
locationAwareLogger.log(firstMarker, fqcn, level, message, argArray, throwable);

这意味着如果你用 Logback 做 marker 过滤(比如只让 AUDIT 标记的日志写入审计文件),之前根本不生效,现在才真正可用。

SimpleLogger 的 WARN 字符串可配置了

slf4j-simple 是 SLF4J 自带的轻量实现,适合测试、CLI 工具、小型项目。它之前渲染日志级别时,WARN 级别硬编码输出 "WARN",而其他级别(如 ERROR)可以通过 simpleLogger.warnLevelString 属性自定义。这导致一个尴尬的情况:你把 ERROR 的显示改成了 "ERR",但 WARN 始终还是 "WARN",风格不统一。

2.0.18 让 renderLevel() 方法在渲染 WARN 时读取 CONFIG_PARAMS.warnLevelString,行为和其他级别对齐了。

配置方式不变,只是现在真的生效:

# simplelogger.properties
org.slf4j.simpleLogger.warnLevelString=WARN
org.slf4j.simpleLogger.errorLevelString=ERR
org.slf4j.simpleLogger.defaultLogLevel=info

之前改 warnLevelString 没效果,现在输出会变成你设定的值。

实践:验证两个修复

下面用一个最小项目验证 marker 传递和 WARN 字符串自定义。

项目结构

slf4j-demo/
├── pom.xml
└── src/main/java/
     └── Demo.java
└── src/main/resources/
     ├── simplelogger.properties
     └── logback-test.xml   用于 marker 测试

pom.xml — 锁定 2.0.18

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>slf4j-demo</artifactId>
  <version>1.0</version>
  <dependencies>
    <!-- Fluent API + marker 所需 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>2.0.18</version>
    </dependency>
    <!-- 场景一:Logback,验证 marker 传递 -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.5.18</version>
    </dependency>
    <!-- 场景二:SimpleLogger,验证 warnLevelString -->
    <!-- 使用时排除 logback,只留 simple -->
  </dependencies>
</project>

场景一:Marker 过滤终于生效

<!-- src/main/resources/logback-test.xml -->
<configuration>
  <!-- 审计日志单独写入文件 -->
  <appender name="AUDIT_FILE" class="ch.qos.logback.core.FileAppender">
    <file>audit.log</file>
    <encoder>
      <pattern>%marker | %msg%n</pattern>
    </encoder>
  </appender>

  <!-- 只接收 AUDIT marker -->
  <logger name="com.example" level="INFO">
    <appender-ref ref="AUDIT_FILE"/>
  </logger>

  <!-- marker 过滤器 -->
  <turboFilter class="ch.qos.logback.classic.turbo.MarkerFilter">
    <Marker>AUDIT</Marker>
    <OnMatch>ACCEPT</OnMatch>
    <OnMismatch>NEUTRAL</OnMismatch>
  </turboFilter>

  <root level="INFO">
    <appender-ref ref="AUDIT_FILE"/>
  </root>
</configuration>
// src/main/java/Demo.java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;

public class Demo {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Demo.class);
        Marker audit = MarkerFactory.getMarker("AUDIT");

        // 带 marker 的日志 — 2.0.18 之前 marker 丢失,现在正常传递
        logger.atInfo().addMarker(audit).log("用户登录成功");

        // 不带 marker 的普通日志
        logger.atInfo().log("普通操作日志");
    }
}

运行后 audit.log 应只包含带 AUDIT 标记的行。2.0.18 之前,marker 传 null,turboFilter 永远匹配不到,审计日志等于废了。

场景二:SimpleLogger 自定义 WARN 显示

切换到 SimpleLogger 时,把 pom.xml 中 logback 依赖换成:

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-simple</artifactId>
  <version>2.0.18</version>
</dependency>
# src/main/resources/simplelogger.properties
org.slf4j.simpleLogger.defaultLogLevel=info
org.slf4j.simpleLogger.warnLevelString=W
org.slf4j.simpleLogger.errorLevelString=E
org.slf4j.simpleLogger.showDateTime=true
org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
// 同一个 Demo.java,简化版
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Demo {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Demo.class);
        logger.warn("配置缺失,使用默认值");
        logger.error("连接超时");
    }
}

预期输出:

HH:mm:ss [W] Demo - 配置缺失使用默认值
HH:mm:ss [E] Demo - 连接超时

2.0.18 之前,WARN 行仍然显示 [WARN] 而不是 [W],和 ERROR 的 [E] 风格不一致。

升级建议

情况 是否需要升级
用 Logback + marker 做日志路由/过滤 必须升级,之前 marker 根本没传下去
slf4j-simple 且自定义了 warnLevelString 必须升级,之前配置被忽略
只用基本 logger.info/warn/error,不带 marker 影响不大,但升级无风险,建议跟上
用 Log4j2 或其他非 LocationAwareLogger 的实现 marker 传递走的是另一条路径,不受此 bug 影响,升级可选

升级方式:把 slf4j-api 版本号从 2.0.x 改到 2.0.18 即可,API 无破坏性变更。如果同时用 slf4j-simplelogback-classic,一并更新到匹配版本。

两个修复都不大,但一个让核心功能(marker)真正可用,一个让轻量实现的配置不再残缺。对于已经在用 SLF4J 2.0 Fluent API 的项目,2.0.18 是值得跟进的版本。


相关推荐