Java日志框架:从Log4j到SLF4J
Java日志框架:从Log4j到SLF4J
Java日志体系概述
Java生态系统中存在多种日志框架,理解它们之间的关系是正确使用日志的第一步。
日志框架层次
应用程序代码
↓
日志门面(SLF4J / JCL)
↓
日志实现(Logback / Log4j2)
- 日志门面:提供统一的API,如SLF4J、Jakarta Commons Logging
- 日志实现:具体的日志输出逻辑,如Logback、Log4j2
SLF4J + Logback
依赖配置
<!-- Maven -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
基本使用
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public User findUser(String id) {
logger.debug("开始查询用户, id={}", id);
try {
User user = userRepository.findById(id);
if (user == null) {
logger.warn("用户不存在, id={}", id);
} else {
logger.info("查询用户成功, id={}, name={}", id, user.getName());
}
return user;
} catch (Exception e) {
logger.error("查询用户失败, id={}", id, e);
throw e;
}
}
}
Logback配置(logback.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
Log4j2
依赖配置
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.21.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.21.1</version>
</dependency>
<!-- SLF4J桥接 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.21.1</version>
</dependency>
Log4j2配置(log4j2.xml)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<RollingFile name="RollingFile" fileName="app.log"
filePattern="app.%d{yyyy-MM-dd}.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.example" level="DEBUG"/>
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFile"/>
</Root>
</Loggers>
</Configuration>
日志最佳实践
1. 使用占位符而非字符串拼接
// 不推荐
logger.info("User " + user.getName() + " logged in");
// 推荐
logger.info("User {} logged in", user.getName());
2. 条件日志避免不必要的开销
if (logger.isDebugEnabled()) {
logger.debug("详细数据: {}", expensiveOperation());
}
3. 异常日志要完整
try {
// 业务逻辑
} catch (Exception e) {
// 正确:传递异常对象
logger.error("操作失败", e);
// 错误:丢失堆栈信息
// logger.error("操作失败: " + e.getMessage());
}
4. 敏感信息脱敏
// 推荐:对敏感字段脱敏
logger.info("用户登录, 手机号={}", maskPhone(user.getPhone()));
private String maskPhone(String phone) {
if (phone == null || phone.length() < 7) return "****";
return phone.substring(0, 3) + "****" + phone.substring(7);
}
MDC(Mapped Diagnostic Context)
MDC可以在日志中附加请求级别的上下文信息:
import org.slf4j.MDC;
public class LogFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
try {
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("userId", getCurrentUserId());
chain.doFilter(req, res);
} finally {
MDC.clear();
}
}
}
Logback配置中使用MDC:
<pattern>%d{HH:mm:ss.SSS} [%thread] [%X{requestId}] %-5level %logger{36} - %msg%n</pattern>
日志级别选择
| 级别 | 适用场景 |
|---|---|
| ERROR | 系统错误、异常、需要立即关注的问题 |
| WARN | 潜在问题、可恢复的异常 |
| INFO | 关键业务流程、系统状态变更 |
| DEBUG | 调试信息、详细的程序运行状态 |
| TRACE | 最详细的跟踪信息 |
总结
正确的日志实践对于应用的监控、调试和运维至关重要。推荐使用SLF4J作为门面,Logback或Log4j2作为实现,遵循最佳实践确保日志的可读性和性能。