← 返回首页

Java日志框架:从Log4j到SLF4J

📂 java ⏱ 2 min 356 words

Java日志框架:从Log4j到SLF4J

Java日志体系概述

Java生态系统中存在多种日志框架,理解它们之间的关系是正确使用日志的第一步。

日志框架层次

应用程序代码
    ↓
日志门面(SLF4J / JCL)
    ↓
日志实现(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作为实现,遵循最佳实践确保日志的可读性和性能。