前端视角 Java Web 入门手册 3.7:常用工具—— slf4j+log4j2

日志是应用程序在运行过程中生成的记录信息,用于追踪应用的行为、状态和错误。日志通常包括以下内容:

  • 时间戳:记录日志生成的具体时间。
  • 日志级别:标识日志的重要性,如 DEBUG、INFO、WARN、ERROR。
  • 消息:具体的日志内容,描述应用的状态或发生的事件。
  • 上下文信息:包括线程名、类名、方法名等,有助于定位问题。

Java 日志简史

Java 日志技术的发展是一个不断演进的过程

  1. log4j 的诞生(2001 年) :瑞士程序员 Ceki Gülcü 开发出 log4j,它凭借出色的性能和灵活的配置,迅速成为 Java 日志领域的佼佼者,并加入 Apache 基金会,成为 Java 日志事实上的标准。
  2. JUL 的出现(2002 年 2 月) :随着 Java 1.4 的发布,Sun 推出了内置的日志库 JUL(Java Util Logging)。JUL 为 Java 开发者提供了便捷的日志实现,无需额外引入第三方库即可使用基本的日志功能。
  3. JCL 的问世(2002 年 8 月) :Apache 推出 JCL(Jakarta Commons Logging),它突破了传统日志库的局限,只定义日志接口,支持在运行时动态加载日志组件的实现。这一特性使得开发者可以根据项目需求灵活切换日志实现,提高了项目的可维护性和扩展性。
  4. SLF4J 和 Logback 的诞生(2006 年) :Ceki 离开 Apache 后,创建了 SLF4J(Simple Logging Facade for Java)和 Logback。SLF4J 作为一个日志门面框架,为不同的日志框架提供了统一的接口;Logback 则是 SLF4J 的高效实现,具备性能卓越、配置灵活等优势。
  5. 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.xmllogback.xml,主要需要配置以下信息

  1. 日志级别(Log Levels) :定义日志的重要性,从低到高包括 TRACE、DEBUG、INFO、WARN、ERROR、FATAL。
  2. Appender:定义日志输出的目标,如控制台、文件、远程服务器等。
  3. Layout/Pattern:定义日志的输出格式,包含时间戳、日志级别、消息等。
  4. Logger:定义日志记录器,通常按包或类进行分类管理。
  5. 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 日志框架,选择时可综合考虑以下方面:

  1. 性能:二者表现都不错。Log4j2 采用异步日志记录等优化技术,在高并发场景优势明显;Logback 内部架构设计高效,能快速输出日志。
  2. 功能特性:Log4j2 配置选项丰富,支持 JSON 配置和自动重新加载;Logback 具备强大的日志滚动策略和日志过滤转换功能。
  3. 易用性:Log4j2 配置简单直观,文档详细;Logback 与 Spring 集成方便,配置文件简洁易懂。
  4. 社区支持和稳定性:两者都有活跃社区和持续更新。不过 Log4j2 曾出现严重安全漏洞,虽已修复但可能让部分用户有顾虑;Logback 稳定性好且无重大问题。
  5. 兼容性:都能与多种 Java 应用服务器和框架良好兼容。Log4j2 提供桥接器便于日志框架切换;Logback 与 Spring 家族框架集成优势显著。

总结来说,若项目对性能、配置灵活性要求高且需处理大规模日志,可选 Log4j2;若基于 Spring 开发,追求与框架无缝集成及简单易用的日志滚动过滤功能,Logback 是更优之选。

SLF4J 怎么知道使用哪个实现类的?

在初始化 SLF4J 的 Logger 对象时,无需直接指定具体的实现类。这是因为 SLF4J 在设计时就考虑到了实现的灵活性和透明性。具体使用哪个实现类是通过以下机制来决定的:

  1. 类路径扫描 :当在代码中调用 LoggerFactory.getLogger(...) 创建 Logger 对象时,SLF4J 会在类路径中扫描实现绑定

  2. 静态绑定选择:根据项目中引入的具体绑定库,例如:

    • 如果类路径中有 SLF4J-log4j12.jar,SLF4J 将绑定到 Log4j 1.2。
    • 如果有 logback-classic.jar,则会绑定到 Logback。
    • 如果有 SLF4J-jdk14.jar,则绑定到 Java 自带的日志记录(java.util.logging)。
    • 如果有 log4j-SLF4J-impl.jar,则绑定到 Log4j 2。
  3. 优先选择:如果类路径中存在多个实现,SLF4J 可能会发出警告,并使用它找到的第一个实现。这种情况下,通常应该确保类路径上只有一个具体的实现绑定来避免冲突。

  4. 初始化过程 :在初次创建 Logger 时,SLF4J 会完成绑定选择过程,并初始化相应的日志实现

  5. 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.
相关推荐
Micro麦可乐14 分钟前
最新Spring Security实战教程(十四)OAuth2.0精讲 - 四种授权模式与资源服务器搭建
java·服务器·spring boot·spring·spring security·oauth2·oauth2授权
进击的小白菜18 分钟前
如何高效实现「LeetCode25. K 个一组翻转链表」?Java 详细解决方案
java·数据结构·leetcode·链表
HelloZheQ1 小时前
Go:简洁高效,构建现代应用的利器
开发语言·后端·golang
悟能不能悟1 小时前
java实现一个操作日志模块功能,怎么设计
java·开发语言
caihuayuan51 小时前
[数据库之十四] 数据库索引之位图索引
java·大数据·spring boot·后端·课程设计
blammmp1 小时前
算法专题四:前缀和
java·开发语言·算法
饕餮争锋2 小时前
Spring普通配置类 vs 自动配置类-笔记
java·笔记·spring
Aimyon_362 小时前
Java复习笔记-基础
java·开发语言·笔记
望未来无悔2 小时前
系统学习算法:动态规划(斐波那契+路径问题)
java·算法
琢磨先生David2 小时前
Java 企业级开发设计模式全解析
java·设计模式