第三章:日志框架:Log4j、Logback、SLF4J 原理与配置,以及我在项目中踩过的坑
日志框架这东西,说白了就是给系统装个「黑匣子」。
没有它,线上出了问题你只能干瞪眼。我见过太多团队,上线前不重视日志,出故障时连个排查线索都没有。嗯,今天我们就来聊聊 Java 生态里最常用的三个日志框架:Log4j、Logback 和 SLF4J。
3.1 三个框架的关系:别搞混了
很多新手容易把 SLF4J 和 Log4j 当成一回事。其实它们分工完全不同。
- SLF4J:一套日志门面(接口)。它不干活,只定义规范。
- Log4j / Logback:具体的日志实现。它们才是真正写日志的。
打个比方:SLF4J 就像餐厅的菜单,Log4j 和 Logback 就是后厨的厨师。你点菜(调用 SLF4J API),厨师(Logback)负责做菜(输出日志)。
核心原则:代码里只依赖 SLF4J,不要直接引用 Log4j 或 Logback 的 API。这样以后换日志实现,一行代码都不用改。
3.2 Log4j:老牌框架,但坑不少
Log4j 1.x 已经停止维护了。如果你还在用,我建议你尽快升级。
我在项目中遇到过最典型的问题:Log4j 1.x 的配置文件是 log4j.properties,格式比较古老。而且它不支持占位符 {},只能用字符串拼接。你想想看,每次打日志都拼字符串,性能能好吗?
// Log4j 1.x 的写法(不推荐)
logger.debug("User " + userId + " login failed, reason: " + reason);
// SLF4J 的写法(推荐)
logger.debug("User {} login failed, reason: {}", userId, reason);
Log4j 2.x 虽然改进了很多,但配置文件和 Logback 不通用。我个人习惯直接上 Logback,省心。
注意:Log4j 1.x 有严重的内存泄漏问题。我曾经在生产环境上遇到过,日志量一大,GC 频繁,最后 OOM 了。排查了两天才找到原因——Log4j 的 AsyncAppender 在队列满时会阻塞,导致线程卡死。
3.3 Logback:Spring Boot 的默认选择
Logback 是 Log4j 作者写的升级版。性能更好,功能更强。
Spring Boot 默认就用 Logback,所以大部分项目其实都在用。它的配置文件是 logback-spring.xml 或 logback.xml。
这里有个常见的坑:日志滚动策略。
我曾经接手过一个项目,日志文件越来越大,最后把磁盘撑爆了。原因就是没配滚动策略。Logback 默认是不滚动的,你得手动配。
<!-- logback-spring.xml 示例 -->
<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/myapp/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天滚动,保留30天 -->
<fileNamePattern>/var/log/myapp/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="FILE"/>
</root>
</configuration>
小技巧:maxHistory 一定要配。我一般设 30 天,既保证能回溯问题,又不会占太多磁盘。另外,totalSizeCap 可以限制总大小,防止日志无限增长。
3.4 SLF4J:门面模式的典范
SLF4J 的设计思想很简单:你只管调用它的 API,具体用哪个实现,由运行时决定。
它的核心是 LoggerFactory.getLogger() 方法。你传一个类进去,它返回一个 Logger 实例。这个实例背后可能是 Logback,也可能是 Log4j,甚至可能是 JDK 自带的 java.util.logging。
我在项目中遇到过一个问题:SLF4J 绑定冲突。
当时项目依赖了多个第三方库,有的用 Log4j,有的用 Logback。结果启动时 SLF4J 报了一堆警告,说找到了多个绑定。虽然能运行,但日志输出混乱,有的日志丢了,有的重复了。
解决办法:排除多余的依赖。
<!-- Maven 中排除 Log4j,只保留 Logback -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
警告:千万不要在 classpath 里同时放 log4j-over-slf4j.jar 和 slf4j-log4j12.jar。这会导致死循环,日志直接打爆 CPU。我曾经踩过这个坑,线上服务直接挂了。
3.5 避坑指南:我踩过的那些坑
做日志框架配置这么多年,我总结了几条血泪教训:
- 异步日志一定要配:同步日志在高并发下会拖慢业务线程。用
AsyncAppender或 Logback 的AsyncLogger,把日志写入操作放到后台线程。 - 日志级别别乱改:生产环境用 INFO 或 WARN,别开 DEBUG。我曾经见过有人把线上日志开到 DEBUG,结果一天产生了几百 GB 日志,磁盘直接爆了。
- 敏感信息别打日志:密码、身份证号、银行卡号,这些绝对不能打。我见过有同事把用户密码明文打出来,被安全部门通报了。
- 日志格式要统一:时间戳、线程名、日志级别、类名、消息,这些字段一个都不能少。方便后续用 ELK 或 Splunk 做分析。
我的建议:团队内部统一一个日志规范。比如所有异常日志必须打印堆栈,所有业务日志必须包含 traceId。这样排查问题时,你才能快速定位到是哪次请求出了问题。
3.6 总结
日志框架看似简单,但用不好就是灾难。SLF4J 是门面,Logback 是实现,两者配合使用最稳妥。Log4j 1.x 能不用就别用了,升级到 Logback 或 Log4j 2.x 吧。
嗯,最后说一句:日志不是写给别人看的,是写给未来的自己看的。你希望未来的自己看到什么样的日志?