目录
[4.1 日志门面](#4.1 日志门面)
[4.2 日志实现](#4.2 日志实现)
[4.3 日志级别](#4.3 日志级别)
[4.4 Appender:输出](#4.4 Appender:输出)
[4.5 Layout / Encoder:格式](#4.5 Layout / Encoder:格式)
[4.6 Logger层次结构](#4.6 Logger层次结构)
[7、配置示例(SLF4J + Logback)](#7、配置示例(SLF4J + Logback))
[8、配置示例(SLF4J + Log4j 2)](#8、配置示例(SLF4J + Log4j 2))
1、使用日志框架原因
为什么不直接用 System.out.println?
-
性能 :
System.out.println是同步的,在高并发场景下会严重拖慢性能。日志框架提供了异步、缓冲等机制。 -
灵活性:可以轻松控制日志输出的目的地(控制台、文件、数据库、网络等)、格式(文本、JSON等)和级别(DEBUG, INFO, WARN, ERROR等)。
-
分级管理:可以在开发环境输出 DEBUG 级别日志,在生产环境只输出 ERROR 级别日志,无需修改代码。
-
运行时控制 :可以通过修改配置文件(如
logback.xml)动态调整日志行为,无需重启应用。 -
丰富的上下文信息:可以方便地输出线程名、类名、方法名、时间戳等。
2、核心价值
-
问题诊断与调试: 当系统出现异常或错误时,日志是定位问题的第一手资料。
-
行为审计: 记录用户的关键操作,满足合规性要求(如金融、政务系统)。
-
性能监控: 通过记录方法的执行时间,分析系统瓶颈。
-
运行状态追踪: 了解系统的运行流程和数据流转,尤其是在复杂的分布式系统中。
-
大数据分析: 日志是重要的数据源,可用于用户行为分析、系统告警等。
3、核心概念

-
日志门面(API层) :提供了统一的日志记录接口,在编码时无需关注底层具体的日志实现。这带来了很大的灵活性,日后更换日志库时,只需调整依赖和配置,而无需修改代码。核心思想是**"门面模式"。**
-
SLF4J 是目前最主流的日志门面。它通过在编译时静态绑定真正的日志库来工作。
-
JCL 是Apache早期的日志门面,现在来看相对陈旧。
-
-
日志实现(库层):这就是真正负责日志输出的"发动机"。
- Logback 、Log4j 2 、JUL 都属于日志实现。
-
桥接器 :这是处理遗留代码或第三方库中已有日志调用的关键。如图中所示,桥接器(例如
log4j-over-slf4j)的作用是将那些原本直接调用旧日志库(如Log4j 1.x, JCL)的API,"路由"或"重定向"到SLF4J门面,从而统一由你选择的日志实现(如Logback或Log4j 2)来处理。这有助于在整个应用中统一日志输出。
桥接机制 :这是 SLF4J 最强大的地方。如果你的项目依赖的第三方库使用了其他日志框架(如 Log4j, JUL, commons-logging),你可以通过"桥接"包,将这些日志调用重定向 到 SLF4J,最终由你统一配置的日志实现(如 Logback)来输出。这样就实现了 "天下归一"。
jcl-over-slf4j.jar:桥接 commons-logging -> SLF4J
log4j-over-slf4j.jar:桥接 Log4j -> SLF4J
jul-to-slf4j.jar:桥接 JUL -> SLF4J
4、核心组件
4.1 日志门面
这是日志的抽象层,它定义了一套统一的日志 API,而不提供具体实现。
-
目的: 解耦。你的应用程序代码只依赖于门面接口,而不依赖于任何具体的日志实现(如 Logback、Log4j2)。这使得你可以在不修改代码的情况下,轻松更换底层的日志库。
-
主流门面:
-
SLF4J: 目前事实上的标准,设计优雅,兼容性好。
-
JCL: 比较老旧,现在已不推荐使用。
-
示例代码(使用 SLF4J):
java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
// 通过门面获取 Logger,通常一个类对应一个 Logger
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
public void process(String data) {
logger.info("开始处理数据: {}", data); // 使用 {} 占位符,高性能
try {
// ... 业务逻辑
} catch (Exception e) {
logger.error("处理数据时发生错误,数据: {}", data, e); // 记录异常栈信息
}
logger.debug("处理数据完成。");
}
}
注意:使用门面,而非具体实现, 在代码中始终依赖 slf4j-api,而不是 logback-classic 或 log4j-api。
绑定机制:SLF4J 在编译时,会寻找类路径上的一个具体实现(称为"绑定")。你引入哪个实现的 JAR 包,它就使用哪个。
slf4j-log4j12.jar:绑定到 Log4j 1.2
slf4j-jdk14.jar:绑定到 java.util.logging (JUL)
slf4j-simple.jar:绑定到 SLF4J 自带的简单实现
logback-classic.jar:天然绑定到 Logback (因为 Logback 原生实现了 SLF4J 的 API)
log4j-slf4j-impl.jar:绑定到 Log4j 2
4.2 日志实现
这是日志功能的具体实现库,负责执行实际的日志记录操作。
-
主流实现:
-
Logback: SLF4J 的原生实现,性能优秀,是 Log4j 的继任者。
-
Log4j 2: Log4j 的升级版,在异步日志性能上极其出色,功能强大。
-
JUL: Java Util Logging,JDK 自带的日志库,但功能相对较弱,一般不作为首选。
-
发展历程和主要派系:
| 时间线 | 派系一:直接实现 | 派系二:门面/抽象层 |
|---|---|---|
| 早期 | Log4j (Ceki Gülcü 创建) 成为事实上的标准,功能强大。 | |
| JSR 标准 | Sun 公司推出 JUL,但设计不如 Log4j,未被广泛采纳。 | |
| 解耦需求 | Apache 推出 commons-logging 作为门面,试图统一 Log4j 和 JUL。 | |
| Log4j 停滞 | Log4j 1.x 停止开发,其作者 Ceki 推出了 SLF4J (门面) 和 Logback (实现) 作为继任者。 | SLF4J + Logback 成为新的事实标准。 |
| 现代 | Apache 重启日志项目,推出 Log4j 2,性能和功能极佳。 | SLF4J + Log4j 2 成为高性能选择。 |
4.3 日志级别
用于控制日志输出的详细程度。级别从低到高通常为:
-
TRACE: 最详细的调试信息,通常用于追踪程序每一步的执行。
-
DEBUG: 调试信息,在开发阶段使用,用于判断程序执行是否正常。
-
INFO: 重要的运行时信息,如系统启动、用户登录、业务操作成功等。
-
WARN: 警告信息,表示可能有问题,但不影响系统运行。
-
ERROR: 错误信息,表示发生了需要被关注的错误,但系统可能仍能继续运行。
-
FATAL: 致命错误,表示系统已经无法继续运行,即将崩溃。
规则: 只会输出不低于 当前设置级别的日志。例如,若设置级别为 INFO,则 INFO、WARN、ERROR、FATAL 级别的日志会被输出,而 DEBUG 和 TRACE 不会。
日志级别规范使用:
-
ERROR:系统发生了错误,必须马上处理。如数据库连接中断、空指针异常等。 -
WARN:一般警告,系统可自动恢复或暂时不影响核心流程。如API调用超时后重试成功。 -
INFO:重要的业务流程节点信息。如用户登录、订单创建。生产环境默认级别。 -
DEBUG:调试信息,用于开发阶段定位问题。不应在生产环境大量输出。 -
TRACE:最详细的日志,比DEBUG更细致。
4.4 Appender:输出
定义了日志的输出目的地。一个 Logger 可以配置多个 Appender。
-
常见类型:
-
ConsoleAppender: 输出到控制台。
-
FileAppender / RollingFileAppender: 输出到文件。
RollingFileAppender支持日志滚动,是生产环境的标配。 -
SocketAppender / SyslogAppender: 输出到网络套接字或系统日志服务。
-
KafkaAppender / DBAppender: 输出到 Kafka 消息队列或数据库。
-
4.5 Layout / Encoder:格式
定义了日志信息的输出格式。
-
PatternLayout: 最常用,通过模式字符串定义格式。
-
示例模式:
%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n -
输出结果:
2023-10-27 14:35:10.123 [http-nio-8080-exec-1] INFO com.example.MyService - 开始处理数据: hello world
-
-
JSON Layout / Encoder: 将日志输出为 JSON 格式,便于后续被 ELK 等日志系统解析。
4.6 Logger层次结构
Logger 通常遵循与 Java 包名相同的层次结构。例如,com.example.service.MyService 的 Logger 是 com.example.service Logger 的子级。
- 继承性: 子 Logger 会继承父 Logger 的配置(如级别、Appender),除非显式覆盖。
5、主流日志框架组合和选型
这里的对比通常指的是Logback和Log4j 2,因为Log4j 1.x已经过时。两者都是现代Java日志的优秀选择,但各有侧重。
| 对比维度 | Logback | Log4j 2 |
|---|---|---|
| 出身与兼容 | 被视为Log4j 1.x的继任者 ,由同一位作者开发。原生实现了SLF4J接口。 | Apache推出的新一代框架,与Log4j 1.x不兼容。通过适配器也支持SLF4J。 |
| 性能表现 | 性能优于Log4j 1.x,异步日志使用阻塞队列。 | 性能更优 ,特别是在异步日志方面。它采用了无锁的LMAX Disruptor环型队列,在高并发下能大幅减少线程阻塞,吞吐量更高。 |
| 配置与功能 | 支持XML/Groovy配置,功能全面,是Spring Boot的默认日志实现。 | 配置更灵活(支持XML/JSON/YAML等),支持热加载 ,提供更丰富的Filters 和Appenders。 |
| 高级特性 | 提供自动压缩、自动清理旧日志文件等特性。 | 支持**"无垃圾"模式** (减少GC压力)、更强大的日志丢弃策略 和等待策略,在高负载下更稳健。 |
-
追求极致性能与高并发 :例如金融交易、大数据处理等场景,Log4j 2 + SLF4J 是更优的选择。
-
常规企业应用、微服务 :特别是基于Spring Boot的项目,使用其默认的 Logback + SLF4J 组合可以简化配置,完全能够满足大部分需求。
-
处理遗留系统或统一日志 :如果项目中有直接调用老日志API(如Log4j 1.x, JCL, JUL)的代码或第三方库,务必使用对应的桥接器 (如
log4j-over-slf4j,jcl-over-slf4j)将它们统一到SLF4J门下,避免日志输出混乱。
重要安全提示 :历史上Log4j 2曾出现严重安全漏洞(CVE-2021-44228,即Log4Shell)。如果选用Log4j 2,务必使用已修复漏洞的最新稳定版本。
一个典型的 Maven 依赖配置 (SLF4J + Logback)
XML
<dependencies>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Logback 实现 (它已经依赖了 slf4j-api 和 logback-core) -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.11</version>
</dependency>
<!-- 如果需要桥接 commons-logging (例如 Spring 4.x 本身用的JCL) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>2.0.7</version>
</dependency>
</dependencies>
一个典型的 Maven 依赖配置 (SLF4J + Log4j 2)
XML
<dependencies>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 绑定 SLF4J 到 Log4j 2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.20.0</version>
</dependency>
<!-- Log4j 2 核心 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
</dependencies>
6、使用注意事项
-
使用占位符
{},而非字符串拼接:-
好:
logger.debug("User {} logged in at {}", userId, loginTime); -
不好:
logger.debug("User " + userId + " logged in at " + loginTime); -
原因: 即使日志级别高于 DEBUG(即这条日志不会输出),字符串拼接操作也会执行,造成不必要的性能损耗。而占位符方式只有在需要输出时才会进行字符串格式化。
-
-
日志信息要具体且有意义:
-
差:
logger.error("Error occurred."); -
好:
logger.error("Failed to create order for user [{}] with product [{}], reason: database connection timeout", userId, productId, exception);
-
-
Exception 处理: 一定要将异常对象作为最后一个参数传入。
logger.error("Something bad happened", e);
-
生产环境必须使用滚动文件: 防止单个日志文件无限增大,占满磁盘。
- 配置策略:按时间滚动(如每天)、按文件大小滚动、同时保留一定时间或数量的历史文件。
-
区分业务日志和访问日志: 使用不同的 Logger 和 Appender 来记录业务逻辑和 HTTP 请求,便于后续分析。
-
使用 MDC 实现分布式追踪:
-
MDC 可以理解为一个与线程绑定的 Map。可以在请求入口处(如 Filter/Interceptor)将一个唯一的
TraceID放入 MDC。 -
在后续的所有日志中,都可以通过配置
%X{TraceID}在日志中自动输出这个 ID。 -
这样,在分布式系统中,通过这个
TraceID就可以串联起一个请求在所有微服务中的完整链路。
-
7、配置示例(SLF4J+ Logback)
一个典型的 logback-spring.xml 配置片段:
XML
<configuration>
<!-- 定义日志格式 -->
<property name="PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%traceId] [%thread] %-5level %logger{36} - %msg%n"/>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<!-- 滚动文件输出 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/myapp.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 按天滚动,并压缩历史文件 -->
<fileNamePattern>logs/myapp.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>30</maxHistory> <!-- 保留30天 -->
</rollingPolicy>
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<!-- 异步Appender,提升性能 -->
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="FILE"/>
</appender>
<!-- 根Logger,级别为INFO -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_FILE"/>
</root>
<!-- 为特定包设置更详细的级别,用于开发调试 -->
<logger name="com.example.service" level="DEBUG"/>
</configuration>
8、配置示例(SLF4J + Log4j 2)
在 src/main/resources 目录下创建 log4j2.xml 配置文件
XML
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="30">
<Properties>
<!-- 定义日志文件存储路径和输出格式 -->
<Property name="LOG_HOME">logs</Property>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property>
<Property name="MAX_FILE_SIZE">100MB</Property>
<Property name="MAX_HISTORY_DAYS">30</Property>
</Properties>
<Appenders>
<!-- 1. 控制台输出 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
</Console>
<!-- 2. 所有日志汇总文件 (按日期和大小滚动) -->
<RollingFile name="RollingFileAll"
fileName="${LOG_HOME}/app-all.log"
filePattern="${LOG_HOME}/app-all-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/> <!-- 按天滚动 -->
<SizeBasedTriggeringPolicy size="${MAX_FILE_SIZE}"/> <!-- 或按文件大小滚动 -->
</Policies>
<DefaultRolloverStrategy max="${MAX_HISTORY_DAYS}"/>
</RollingFile>
<!-- 3. 错误级别日志独立文件 -->
<RollingFile name="RollingFileError"
fileName="${LOG_HOME}/app-error.log"
filePattern="${LOG_HOME}/app-error-%d{yyyy-MM-dd}-%i.log.gz">
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="${MAX_FILE_SIZE}"/>
</Policies>
<DefaultRolloverStrategy max="100"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- 你可以在此为特定包或类配置独立的日志级别和Appender -->
<!-- <Logger name="com.yourcompany.yourproject" level="DEBUG" additivity="false">
<AppenderRef ref="Console"/>
</Logger> -->
<!-- 根日志记录器配置 -->
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileAll"/>
<AppenderRef ref="RollingFileError"/>
</Root>
</Loggers>
</Configuration>