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-simple 或 logback-classic,一并更新到匹配版本。
两个修复都不大,但一个让核心功能(marker)真正可用,一个让轻量实现的配置不再残缺。对于已经在用 SLF4J 2.0 Fluent API 的项目,2.0.18 是值得跟进的版本。