一、概述
Java日志框架是开发中记录和管理日志的重要工具,合理选择和使用日志组件能提升项目的可维护性和灵活性。
1、核心概念
- 日志门面(Logging Facade)
- 作用:提供统一的API接口,解耦业务代码与具体日志实现。
- 优势:允许灵活切换底层日志库,无需修改代码。
- 常见门面:SLF4J、JCL(Apache Commons Logging)。
- 日志实现(Logging Implementation)
- 作用:实际处理日志输出的具体库。
- 常见实现:Log4j 2.x、Logback、JUL(java.util.logging)。
- 桥接器(Bridging)
- 作用:将其他日志API调用重定向到门面或指定实现。
- 示例:jcl-over-slf4j(将JCL调用转到SLF4J)、log4j-to-slf4j(将Log4j 2.x调用转到SLF4J)。
2、主流技术栈对比
(1)日志门面
- SLF4J(Simple Logging Facade for Java)
- 通过LoggerFactory加载日志具体的实现对象,在绑定具体实现的时候,通过类加载器,加载
org/slf4j/impl/StaticLoggerBinder.class
- 绑定实现:需配合具体实现(如Logback、Log4j 2.x)使用。
- 特点:社区活跃,支持参数化日志(logger.debug(“Value: {}”, value)),避免字符串拼接开销。
- 通过LoggerFactory加载日志具体的实现对象,在绑定具体实现的时候,通过类加载器,加载
- JCL(Jakarta Commons Logging)
- 通过LogFactory动态加载Log实现类(运行时决定)
- 已逐渐被SLF4J取代。
(2)日志实现
- Log4j 2.x
- 优势:异步日志、高性能插件架构、支持多种配置格式(XML/JSON/YAML)。
- 功能:异步Logger、自定义日志级别、过滤机制。
- Logback
- 优势:SLF4J原生实现,性能优于Log4j 1.x,支持自动重载配置。
- 功能:条件化配置、SiftingAppender(按上下文分离日志)。
- JUL(Java Util Logging)
- 特点:JDK内置,无需额外依赖,但功能较少,配置不够灵活。
3、典型组合方案
3.1 SLF4J + Logback
<!-- SLF4J 门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<!-- Logback 实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.3</version>
</dependency>
3.2 SLF4J + Log4j 2.x
<!-- SLF4J 门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.12</version>
</dependency>
<!-- Log4j 2适配器 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.23.1</version>
</dependency>
<!-- Log4j 2核心 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
</dependency>
4、桥接旧项目示例
若旧项目使用Log4j 1.x或JCL,可通过桥接器统一到SLF4J:
- Log4j 1.x → SLF4J
<dependency> <groupId>org.slf4j</groupId> <artifactId>log4j-over-slf4j</artifactId> <version>2.0.12</version> </dependency> <!-- 排除原Log4j 1.x依赖 -->
- JCL → SLF4J
<dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>2.0.12</version> </dependency>
二、Log4j2
Apache Log4j 2 是 Java 社区中广泛使用的高性能日志框架,作为 Log4j 1.x 的升级版本,它在架构设计、性能和功能上进行了全面优化。
1、Log4j 2 的核心优势
- 高性能
- 异步日志(Asynchronous Logging):通过 AsyncLogger 实现非阻塞日志写入,支持 LMAX Disruptor 库,大幅提升吞吐量(尤其在多线程场景)。
- 低垃圾生成(Low Garbage Collection):通过对象重用和缓冲区设计减少内存分配,降低 GC 压力。
- 灵活的插件架构
- 模块化设计:支持自定义 Appender、Filter、Layout 等组件,通过注解 @Plugin 轻松扩展功能。
- 丰富的内置插件:如 KafkaAppender、CassandraAppender、NoSQLAppender 等,适配多种日志存储场景。
- 多配置格式支持
- XML/JSON/YAML/Properties:支持多种配置文件格式,推荐使用 XML 或 JSON 实现复杂配置。
- 动态重载配置:修改配置文件后无需重启应用,自动检测并应用新配置。
- 强大的过滤机制
- 条件化日志输出:通过 ThresholdFilter、RegexFilter 等按日志级别、内容动态过滤。
- 复合过滤:使用 CompositeFilter 组合多个过滤规则。
- 安全增强
- 漏洞修复:Log4j 2.17+ 修复了 Log4Shell(CVE-2021-44228)等安全漏洞,默认禁用 JNDI 查找。
- 严格模式:通过配置 log4j2.formatMsgNoLookups=true 进一步限制动态解析。
2、核心组件与概念
(1)组成
- 日志信息的输出目的地:日志信息的输出目的地指定了日志将打印到控制台还是文件中;
- 日志信息的输出格式:而输出格式则控制了日志信息的显示内容。
- 日志信息的优先级:TRACE < DEBUG < INFO < WARN < ERROR < FATAL
(2)组件层级
组件 | 作用 |
---|---|
Logger | 日志记录器,通过名称层次化继承配置(如 com.example.service)。 |
Appender | 定义日志输出目的地(控制台、文件、数据库、消息队列等)。 |
Layout | 指定日志格式(如时间、级别、类名、自定义字段)。 |
Filter | 过滤日志事件,决定是否输出或传递给下级组件。 |
(3)日志级别(Level)
内置级别(从低到高):
- TRACE:追踪,是最低的日志级别,相当于追踪程序的执行
- DEBUG:调试,一般在开发中,都将其设置为最低的日志级别
- INFO:信息
- WARN:警告,有些信息不是错误信息,但也要给程序员一些提示。
- ERROR:错误
- FATAL:严重错误
如果一条日志信息的级别大于等于配置文件的级别就记录。
支持自定义级别,但需谨慎使用以避免兼容性问题。
(4)输出源
- CONSOLE(输出到控制台)
- FILE(输出到文件)
(5)格式
- SimpleLayout:以简单的形式显示
- HTMLLayout:以HTML表格显示
- PatternLayout:自定义形式显示
- %d{yyyy-MM-dd HH:mm:ss, SSS} : 日志生产时间,输出到毫秒的时间
- %-5level : 输出日志级别,-5表示左对齐并且固定输出5个字符,如果不足在右边补0
- %c : logger的名称(%logger)
- %t : 输出当前线程名称
- %p : 日志输出格式
- %m : 日志内容,即 logger.info(“message”)
- %n : 换行符
- %C : Java类名(%F)
- %L : 行号
- %M : 方法名
- %l : 输出语句所在的行数, 包括类名、方法名、文件名、行数
- hostName : 本地机器名
- hostAddress : 本地ip地址
(6)异步日志模式
- 全局异步(AsyncLogger):所有日志异步处理,需引入 disruptor 依赖。
- 混合异步(AsyncAppender):将同步日志缓冲后异步写入,适用于部分异步场景。
3、配置文件详解(以 XML 为例)
Log4j 2 默认加载类路径下的 log4j2.xml,也可通过 -Dlog4j.configurationFile
指定路径。
(1)最小化配置示例
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<!-- 控制台输出 -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<!-- 文件滚动归档 -->
<RollingFile name="File" fileName="logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="%d{ISO8601} %-5level [%t] %c{1.} - %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
<DefaultRolloverStrategy max="10"/>
</RollingFile>
</Appenders>
<Loggers>
<!-- 自定义 Logger(如调试特定包) -->
<Logger name="com.example.service" level="DEBUG" additivity="false">
<AppenderRef ref="File"/>
</Logger>
<!-- 根 Logger -->
<Root level="INFO">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
配置参数说明:
<Appenders>
:,如 Console、File、RollingFile、Kafka 等。<Loggers>
:定义日志记录规则,可继承父 Logger 配置,additivity=“false” 阻止传递给父 Logger。<Filters>
:在 Logger 或 Appender 中定义过滤条件(如 ThresholdFilter)。
log4j2配置节点说明:
- Configuration:根节点
- 属性:
- status:用来指定log4j本身的打印日志的级别。
- monitorinterval:用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s.
- 子节点:Appenders + Loggers
Appenders
:定义输出目标- 子节点:Console + File + RollingFile
Console
节点:用来定义输出到控制台的Appender。- 属性:
- name:指定Appender的名字.
- target:SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT.
- 子节点:
PatternLayout
:输出格式,不设置默认为:%m%n.
- 属性:
File
节点:用来定义输出到指定位置的文件的Appender.- 属性:
- name:指定Appender的名字.
- fileName:指定输出日志的目的文件带全路径的文件名.
- 子节点:
PatternLayout
:输出格式,不设置默认为:%m%n.
- 属性:
RollingFile
节点:用来定义超过指定条件自动删除旧的创建新的Appender.- 属性:
- name:指定Appender的名字.
- fileName:指定输出日志的目的文件,带全路径的文件名。
- filePattern:指定当发生Rolling时,文件的转移和重命名规则.
- 子节点:
PatternLayout
:输出格式,不设置默认为:%m%n.Policies
:指定滚动日志的策略,就是什么时候进行新建日志文件输出日志.TimeBasedTriggeringPolicy
:基于时间的滚动策略,- interval:用来指定多久滚动一次,默认是1 hour。
- modulate:用于调整时间,确保滚动发生在整点或指定的时间间隔。
SizeBasedTriggeringPolicy
:基于指定文件大小的滚动策略。- size:用来定义每个日志文件的大小。
DefaultRolloverStrategy
:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)。
- 属性:
- 子节点:Console + File + RollingFile
Loggers
节点:Root + Logger.Root
节点:用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出- 属性:
level
:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
- 子节点:
appender-ref
:Root的子节点,用来指定该日志输出到哪个Appender.
- 属性:
Logger
节点:用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。- 属性:
name
:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点.level
:指定子节点AppenderRef的日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF。
- 子节点:
appender-ref
:Logger的子节点,用来指定该日志输出到哪个Appender。如果没有指定,就会默认继承自Root(注意,日志级别也是root设置的日志级别)。如果指定了,那么会在指定的这个Appender和Root的Appender中都会有输出,此时我们可以设置Logger的属性additivity=“false” 来限制只在自定义的Appender中进行输出。
- 属性:
- 属性:
Log4j2还支持其他类型的Appender,如AsyncAppender(异步输出)、RandomAccessFileAppender、RollingRandomAccessFileAppender等。
4、高级特性
4.1 异步日志配置
全局异步(需 disruptor 依赖)
<Configuration>
<AsyncLogger name="com.example" level="DEBUG" includeLocation="true"/>
<AsyncRoot level="INFO">
<AppenderRef ref="Console"/>
</AsyncRoot>
</Configuration>
引入 Maven 依赖:
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.4</version>
</dependency>
4.2 自定义日志字段
使用 ThreadContext(即 MDC)添加上下文信息:
ThreadContext.put("requestId", UUID.randomUUID().toString());
logger.info("Processing request");
ThreadContext.clear();
在 Layout 中引用:
<PatternLayout pattern="%d{ISO8601} [%X{requestId}] %msg%n"/>
4.3 日志分离(RoutingAppender)
按条件动态路由日志到不同 Appender:
<Routing name="RoutingAppender">
<Routes pattern="$${ctx:logType}">
<Route key="AUDIT">
<File name="AuditLog" fileName="logs/audit.log"/>
</Route>
<Route key="DEBUG">
<Console name="Console"/>
</Route>
</Routes>
</Routing>
5、 集成日志门面
5.1 使用log4j-api做门面
虽然Log4j2也是日志门面,因为它的日志实现功能非常强大,性能优越。所以大家一般还是将Log4j2看作是日志的实现。
<!-- Log4j2 门面API-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
<!-- Log4j2 日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
5.2 使用slf4j做门面
<!--使用slf4j作为日志的门面,使用log4j2来记录日志 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.14.1</version> <!-- 确保版本一致 -->
</dependency>
5.3 SpringBoot + slf4j + log4j2
- 导入依赖
如果用到<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- SLF4J 门面 + Log4j2 实现 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> <version>3.2.0</version> </dependency>
Mybatis-Plus
也需要排除默认日志依赖。SLF4J: Class path contains multiple SLF4J providers. SLF4J: Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@e874448] SLF4J: Found provider [org.apache.logging.slf4j.SLF4JServiceProvider@29b5cd00] SLF4J: See https://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual provider is of type [ch.qos.logback.classic.spi.LogbackServiceProvider@e874448]
- 配置文件:
- 在项目的
resource
目录下新建文件log4j2-spring.xml
,如果自定义其他名称需在application.yml
中配置:logging: config: classpath:log4j2.xml
- 配置文件
log4j2-spring.xml
模版:<?xml version="1.0" encoding="UTF-8"?> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration monitorInterval="5"> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--变量配置--> <Properties> <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符--> <!-- %logger{36} 表示 Logger 名字最长36个字符 --> <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> <!-- 定义日志存储的路径 --> <property name="FILE_PATH" value="./log/" /> <property name="FILE_NAME" value="file.log" /> </Properties> <appenders> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="${LOG_PATTERN}"/> <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> </console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用--> <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false"> <PatternLayout pattern="${LOG_PATTERN}"/> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="${LOG_PATTERN}"/> <Policies> <!--interval属性用来指定多久滚动一次,默认是1 hour--> <TimeBasedTriggeringPolicy interval="1"/> <SizeBasedTriggeringPolicy size="10MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖--> <DefaultRolloverStrategy max="15"/> </RollingFile> </appenders> <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。--> <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.mybatis" level="info" additivity="false"> <AppenderRef ref="Console"/> </logger> <!--监控系统信息--> <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。--> <Logger name="org.springframework" level="info" additivity="false"> <AppenderRef ref="Console"/> </Logger> <root level="info"> <appender-ref ref="Console"/> <appender-ref ref="Filelog"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </loggers> </configuration>
- 在项目的
- 在代码中使用
- 直接使用
slf4j
public class Demo { private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExampleOther.class); public static void main(String... args) { log.error("Something else is wrong here"); } }
- 搭配 Lombok
<!-- 导入 Lombok 依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> </dependency>
import lombok.extern.log4j.Log4j2; @Log4j2 public class LogTest { public static void main(String[] args) { log.info("this is info log"); log.error("this is error log"); log.debug("this is debug log"); log.warn("this is warn log"); log.trace("this is trace log"); log.fatal("this is fatal log"); } }
- 直接使用
6、最佳实践
- 避免日志性能问题
- 使用异步日志时,设置合理的缓冲区大小(bufferSize)。
- 避免在高频代码中频繁记录 DEBUG 级别日志,可通过 logger.isDebugEnabled() 预先判断。
- 配置分离与动态化
- 使用 ${env:VARIABLE} 引用环境变量,实现环境差异化配置。
- 将日志配置拆分为多个文件,通过
<Include>
引入。
- 日志归档与清理
- 使用 RollingFile 结合时间和大小策略,避免单个文件过大。
- 配置 DefaultRolloverStrategy 的 max 参数限制历史文件数量。
- 监控与告警
- 集成 PrometheusAppender 或 HTTPAppender 将日志发送到监控系统。
- 使用 SMTPAppender 在发生 ERROR 日志时触发邮件告警。