日志是应用程序在运行过程中生成的记录信息,用于追踪应用的行为、状态和错误。日志通常包括以下内容:
- 时间戳:记录日志生成的具体时间。
- 日志级别:标识日志的重要性,如 DEBUG、INFO、WARN、ERROR。
- 消息:具体的日志内容,描述应用的状态或发生的事件。
- 上下文信息:包括线程名、类名、方法名等,有助于定位问题。
Java 日志简史
Java 日志技术的发展是一个不断演进的过程
- log4j 的诞生(2001 年) :瑞士程序员 Ceki Gülcü 开发出 log4j,它凭借出色的性能和灵活的配置,迅速成为 Java 日志领域的佼佼者,并加入 Apache 基金会,成为 Java 日志事实上的标准。
- JUL 的出现(2002 年 2 月) :随着 Java 1.4 的发布,Sun 推出了内置的日志库 JUL(Java Util Logging)。JUL 为 Java 开发者提供了便捷的日志实现,无需额外引入第三方库即可使用基本的日志功能。
- JCL 的问世(2002 年 8 月) :Apache 推出 JCL(Jakarta Commons Logging),它突破了传统日志库的局限,只定义日志接口,支持在运行时动态加载日志组件的实现。这一特性使得开发者可以根据项目需求灵活切换日志实现,提高了项目的可维护性和扩展性。
- SLF4J 和 Logback 的诞生(2006 年) :Ceki 离开 Apache 后,创建了 SLF4J(Simple Logging Facade for Java)和 Logback。SLF4J 作为一个日志门面框架,为不同的日志框架提供了统一的接口;Logback 则是 SLF4J 的高效实现,具备性能卓越、配置灵活等优势。
- log4j2 的发布(2012 年) :Apache 发布 log4j2,它整合了 Logback 的优秀特性,并在性能、稳定性和功能上进行了全面升级,成为 Java 日志领域的又一强大工具。
日志框架搭配
SLF4J(Simple Logging Facade for Java)是一个日志门面框架,它提供了一种简单的日志记录接口。为不同的日志框架(如 logback、log4j)提供一个统一的接口,使应用程序能够方便地切换日志框架而不影响应用程序的其他部分
logback 和 log4j 都是 Java 的日志框架,它们提供了具体的日志实现。SLF4J 可以与这些日志框架配合使用,提供一个统一的日志接口,并将日志记录的任务委托给具体的日志实现框架(如 logback、log4j)来完成。
门面模式(Facade Pattern)是一种结构型设计模式,它提供了一个简单的接口,隐藏了一组复杂的子系统接口,使得客户端可以更加方便地使用这个子系统。
在门面模式中,客户端只需要调用一个门面对象的方法,门面对象内部就会调用子系统的多个方法,并将结果返回给客户端。客户端不需要知道子系统的具体实现细节,只需要知道门面对象的使用方法即可。
门面模式有助于降低系统的复杂性,提高系统的可维护性和可扩展性。它可以将复杂的子系统隐藏在一个门面对象后面,使得客户端只需要与门面对象打交道,不需要直接操作子系统对象。这样当子系统发生变化时,只需要修改门面对象,而不需要修改客户端代码
应用中不可直接使用日志系统(log4j、logback)中的 API ,而应依赖使用日志框架 SLF4J 中的 API 。使用门面模式的日志框架,有利于维护和各个类的日志处理方式的统一
日志配置
Log4j2 和 Logback 配置文件通常位于src/main/resources
目录下,文件名分别为log4j2.xml
和logback.xml
,主要需要配置以下信息
- 日志级别(Log Levels) :定义日志的重要性,从低到高包括 TRACE、DEBUG、INFO、WARN、ERROR、FATAL。
- Appender:定义日志输出的目标,如控制台、文件、远程服务器等。
- Layout/Pattern:定义日志的输出格式,包含时间戳、日志级别、消息等。
- Logger:定义日志记录器,通常按包或类进行分类管理。
- Filter:过滤日志,按照特定条件决定是否记录日志。
xml
<?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.SSS} %-5p %l %m%n" />
</Console>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
Log4j2 使用
添加 Maven 依赖
xml
<dependencies>
<!-- Log4j2 API -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.20.0</version>
</dependency>
<!-- Log4j2 核心实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
</dependencies>
添加配置文件
创建log4j2.xml
文件,放置在src/main/resources
目录下:
xml
<?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="FileLogger" fileName="logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd}.log.gz">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5level %logger{36} - %msg%n</Pattern>
</PatternLayout>
<Policies>
<!-- 按日期归档 -->
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!-- 异步日志记录 -->
<Async name="AsyncLogger">
<AppenderRef ref="Console"/>
<AppenderRef ref="FileLogger"/>
</Async>
</Appenders>
<Loggers>
<!-- 根日志级别 -->
<Root level="info">
<AppenderRef ref="AsyncLogger"/>
</Root>
<!-- 特定包的日志级别 -->
<Logger name="com.example" level="debug" additivity="false">
<AppenderRef ref="AsyncLogger"/>
</Logger>
</Loggers>
</Configuration>
- Appenders :定义日志的输出目标,包括控制台和文件。
- PatternLayout :定义日志的输出格式。
- %-5level 输出日志级别,-5表示左对齐并且固定输出5个字符
- %logger 输出 logger 名称
- %n 换行
- %m 日志内容
- %F 输出所在的类文件名
- %L 输出行号
- %M 输出所在方法名
- %l 输出语句所在的行数, 包括类名、方法名、文件名、行数
- RollingFile :定义日志文件的滚动策略,按日期归档并压缩为
.gz
文件。 - Async :异步记录日志,提升性能,减少对主业务线程的影响。
- Loggers :定义日志记录器,包括根日志记录器和特定包的日志记录器。
编写代码
java
package com.example;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Example {
private static final Logger logger = LogManager.getLogger(Log4j2Example.class);
public static void main(String[] args) {
logger.info("应用程序启动");
logger.debug("这是一个DEBUG级别的日志");
logger.warn("这是一个WARN级别的日志");
logger.error("这是一个ERROR级别的日志");
logger.fatal("这是一个FATAL级别的日志");
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
logger.error("发生异常:", e);
}
}
}
运行上述代码后,日志将被输出到控制台和logs/app.log
文件中,并根据配置自动归档。
控制台输出示例:
plain
2025-02-10 15:00:00 [main] INFO com.example.Log4j2Example - 应用程序启动
2025-02-10 15:00:00 [main] DEBUG com.example.Log4j2Example - 这是一个DEBUG级别的日志
2025-02-10 15:00:00 [main] WARN com.example.Log4j2Example - 这是一个WARN级别的日志
2025-02-10 15:00:00 [main] ERROR com.example.Log4j2Example - 这是一个ERROR级别的日志
2025-02-10 15:00:00 [main] FATAL com.example.Log4j2Example - 这是一个FATAL级别的日志
2025-02-10 15:00:00 [main] ERROR com.example.Log4j2Example - 发生异常:
java.lang.ArithmeticException: / by zero
at com.example.Log4j2Example.main(Log4j2Example.java:14)
日志文件 logs/app.log
:内容与控制台类似,且会根据日期自动归档为压缩文件。
Logback 使用
添加依赖
在Maven项目中添加Logback的相关依赖:
xml
<dependencies>
<!-- SLF4J API -->
<dependency>
<groupId>org.SLF4J</groupId>
<artifactId>SLF4J-api</artifactId>
<version>2.0.7</version>
</dependency>
<!-- Logback经典实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
</dependencies>
添加配置文件
创建一个logback.xml
文件,放置在src/main/resources
目录下
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 文件输出,自动归档 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天一个日志文件,保留30天 -->
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 异步日志记录 -->
<appender name="ASYNC_CONSOLE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="CONSOLE"/>
</appender>
<!-- 日志根级别 -->
<root level="INFO">
<appender-ref ref="ASYNC_CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<!-- 特定包的日志级别 -->
<logger name="com.example" level="DEBUG" additivity="false">
<appender-ref ref="ASYNC_CONSOLE"/>
<appender-ref ref="FILE"/>
</logger>
</configuration>
编写代码
可以使用 lombok 提供的 @SLF4J 自动生成 Logger 对象
java
package com.example;
import org.SLF4J.Logger;
import org.SLF4J.LoggerFactory;
import lombok.extern.SLF4J.SLF4J;
@SLF4J(topic="log") // log 也是默认值
public class LogbackExample {
public static void main(String[] args) {
logger.info("应用程序启动");
logger.debug("这是一个DEBUG级别的日志");
logger.warn("这是一个WARN级别的日志");
logger.error("这是一个ERROR级别的日志");
try {
int result = 10 / 0;
} catch (ArithmeticException e) {
logger.error("发生异常:", e);
}
}
}
运行上述代码后,日志将被输出到控制台和logs/app.log
文件中,并根据配置自动归档
Log4j2 与 Logback 的选择
Log4j2 和 Logback 都是优秀的 Java 日志框架,选择时可综合考虑以下方面:
- 性能:二者表现都不错。Log4j2 采用异步日志记录等优化技术,在高并发场景优势明显;Logback 内部架构设计高效,能快速输出日志。
- 功能特性:Log4j2 配置选项丰富,支持 JSON 配置和自动重新加载;Logback 具备强大的日志滚动策略和日志过滤转换功能。
- 易用性:Log4j2 配置简单直观,文档详细;Logback 与 Spring 集成方便,配置文件简洁易懂。
- 社区支持和稳定性:两者都有活跃社区和持续更新。不过 Log4j2 曾出现严重安全漏洞,虽已修复但可能让部分用户有顾虑;Logback 稳定性好且无重大问题。
- 兼容性:都能与多种 Java 应用服务器和框架良好兼容。Log4j2 提供桥接器便于日志框架切换;Logback 与 Spring 家族框架集成优势显著。
总结来说,若项目对性能、配置灵活性要求高且需处理大规模日志,可选 Log4j2;若基于 Spring 开发,追求与框架无缝集成及简单易用的日志滚动过滤功能,Logback 是更优之选。
SLF4J 怎么知道使用哪个实现类的?
在初始化 SLF4J 的 Logger
对象时,无需直接指定具体的实现类。这是因为 SLF4J 在设计时就考虑到了实现的灵活性和透明性。具体使用哪个实现类是通过以下机制来决定的:
-
类路径扫描 :当在代码中调用
LoggerFactory.getLogger(...)
创建Logger
对象时,SLF4J 会在类路径中扫描实现绑定 -
静态绑定选择:根据项目中引入的具体绑定库,例如:
- 如果类路径中有
SLF4J-log4j12.jar
,SLF4J 将绑定到 Log4j 1.2。 - 如果有
logback-classic.jar
,则会绑定到 Logback。 - 如果有
SLF4J-jdk14.jar
,则绑定到 Java 自带的日志记录(java.util.logging)。 - 如果有
log4j-SLF4J-impl.jar
,则绑定到 Log4j 2。
- 如果类路径中有
-
优先选择:如果类路径中存在多个实现,SLF4J 可能会发出警告,并使用它找到的第一个实现。这种情况下,通常应该确保类路径上只有一个具体的实现绑定来避免冲突。
-
初始化过程 :在初次创建
Logger
时,SLF4J 会完成绑定选择过程,并初始化相应的日志实现 -
Classpath Binding Report:如果多个 SLF4J 绑定存在于类路径中,SLF4J 会输出类似如下的警告:
plain
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/path/to/SLF4J-log4j12-1.x.x.jar!]
SLF4J: Found binding in [jar:file:/path/to/SLF4J-jdk14-1.x.x.jar!]
SLF4J: See http://www.SLF4J.org/codes.html#multiple_bindings for an explanation.