Java日志系统

日志系统

前言

-Dlogback.configurationFile=D:\file\IDEA_File\logDemo\config\logback-main.xml

JAVA日志框架的使用_java logger-CSDN博客

(7 封私信) Java 日志管理的黄金组合: SLF4J+Logback - 知乎

image-20251105144735333

详细历史背景

1. 混沌初期(2001年之前)

  • 各自为战:每个项目、每个开发者都有自己的日志方式。
  • System.out.println:最简单的调试输出。
  • Log4j 1.x第一个真正意义上的专业日志框架,由Ceki Gülcü开发,后来捐赠给Apache。它功能强大,成为事实标准。

2. JDK的尝试(2002年)

  • Sun公司在JDK 1.4中引入了 java.util.logging,希望统一日志标准。
  • 但问题来了
    • JUL设计相对简陋,性能不如Log4j
    • 此时Log4j已经广泛流行,很多项目不愿意迁移
    • 出现了Log4j vs JUL的阵营分裂

3. 门面模式的诞生(2005-2006年)

为什么需要门面?

想象一下这个场景:

  • 你的项目使用了Library A,它用Log4j写日志
  • 你又引入了Library B,它用JUL写日志
  • 现在你的应用要配置两套日志系统,输出格式不统一,难以管理

解决方案:门面模式

  1. JCL登场:Apache推出了Commons Logging,这是第一个主流的日志门面。
    • 你的代码面向JCL API
    • JCL在运行时通过类加载器机制自动发现底层的Log4j或JUL
    • 但JCL有著名的类加载器问题,在复杂的Web容器中经常出问题
  2. SLF4J回应:Log4j的作者Ceki Gülcü对JCL不满意,开发了SLF4J
    • 解决JCL的类加载器问题
    • 采用静态绑定,更可靠
    • 提供更优雅的API(如 {} 占位符,避免字符串拼接性能开销)

4. 现代阶段

  • Logback:Ceki Gülcü想彻底改进Log4j,但不愿破坏兼容性,于是重写了Logback,原生实现SLF4J API
  • Log4j 2:Apache社区看到Logback的成功,决定重写Log4j,吸取了Logback的优点,但保持了独立API

Java主要日志框架历史沿革总结

名称 出现时间 制作人/组织 类型 设计目标/特点 现状评价
Log4j 1.x 1999-2001 Ceki Gülcü (后捐赠给Apache) 日志框架 第一个专业的Java日志框架,功能完整强大 已淘汰,存在性能问题和内存泄漏
JUL (java.util.logging) 2002 (JDK 1.4) Sun Microsystems 日志框架 JDK内置,希望统一日志标准 仍在使用,但功能性能一般,多用于简单项目
JCL (Apache Commons Logging) 2003-2005 Apache 日志门面 第一个主流日志门面,解决多日志框架共存问题 逐渐被替代,有著名的类加载器问题
SLF4J 2005-2006 Ceki Gülcü 日志门面 解决JCL的缺陷,提供更优雅的API和可靠绑定 当前主流,事实上的标准门面
Logback 2006 Ceki Gülcü 日志框架 Log4j的精神继任者,原生实现SLF4J API 当前主流,性能优秀,与SLF4J完美配合
Log4j 2 2012-2014 Apache 日志框架 完全重写,吸取Logback优点,支持异步和插件 当前主流,高性能,功能最丰富

重要桥接组件

名称 出现时间 作用 使用场景
log4j-over-slf4j 2006左右 将Log4j 1.x API调用桥接到SLF4J 迁移老旧项目,统一日志门面
jcl-over-slf4j 2006左右 将Commons Logging调用桥接到SLF4J 解决JCL的类加载器问题
jul-to-slf4j 2006左右 将java.util.logging调用桥接到SLF4J 将JUL日志统一到SLF4J体系
log4j-slf4j-impl 2012左右 SLF4J到Log4j 2的绑定器 在使用Log4j 2作为实现时使用
log4j-to-slf4j 2012左右 将Log4j 2 API调用桥接到SLF4J 需要将Log4j2 API调用重定向到SLF4J时

关键时间节点图示

image-20251105195847798

总结

  1. 先驱时代:Log4j 1 → JUL(各自为战)
  2. 门面诞生:JCL → SLF4J(解决兼容性问题)
  3. 现代时代:Logback → Log4j 2(性能与功能的竞争)

当前推荐组合:**SLF4J(门面) + Log4j 2/Logback(实现)**

一个日志框架的使用

SLF4J(门面) + Logback(实现)

项目依赖 (Maven)
<!-- java 11 -->
<dependencies>
    <!-- SLF4J 门面 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.9</version>
    </dependency>
    <!-- Logback 实现 (包含SLF4J绑定) -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.11</version>
    </dependency>
</dependencies>

<!-- java 8 -->
<dependencies>
    <!-- SLF4J 门面 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <!-- Logback 实现 (包含SLF4J绑定) -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
    </dependency>
</dependencies>
配置文件:src/main/resources/logback.xml
  • 默认找src/main/resources/下的logback.xml
  • 也可以配置指定文件,优先级比默认的高:
    • -Dlogback.configurationFile=D:\file\IDEA_File\mu4_mu3h5\mu4-mu3h5\config\config-logic\logback-main.xml

默认查找顺序

  1. 看有没有配置-Dlogback.configurationFile=,优先用该配置

  2. 类路径中查找以下文件(按优先级顺序):

    • `logback.groovy``
    • ``logback-test.xml`
    • logback.xml`
  3. 如果以上都找不到,Logback 会使用:

    • ch.qos.logback.classic.BasicConfigurator
    • 这个基础配置器会设置一个最小化配置,将日志输出到控制台
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
    <!-- 控制台输出 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

<!--    &lt;!&ndash; 文件输出 - 简化版本 &ndash;&gt;-->
<!--    <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}.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>-->

    <!-- 文件输出 - 使用 SizeAndTimeBasedRollingPolicy -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>logs/myapp.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>logs/myapp.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <maxFileSize>100MB</maxFileSize>
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </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="CONSOLE" />
        <appender-ref ref="FILE" />
    </root>

    <!-- 特定包日志级别 -->
<!--    <logger name="com.example.service" level="INFO"/>-->
</configuration>

控制台输出:

<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="true" scanPeriod="5 seconds" debug="true">
    <!--LevelFilter:      级别 == 配置级别-->
    <!--ThresholdFilter:  级别 >= 配置级别-->
    
    <!-- Appender内部:过滤器是顺序的(链式处理) -->
	<!-- Appender之间:过滤器是并行的(独立处理) -->
	<!-- 在单个Appender内部,过滤器确实是按配置顺序执行的! -->
    
    <!-- 控制台 INFO-->
    <appender name="consoleInfoAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss} [%thread] %.-5level]%C{0}:%L %msg%n</pattern>
        </encoder>
        <target>System.out</target>

        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 控制台 DEBUG-->
    <appender name="consoleDebugAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss} [%thread] %.-5level]%C{0}:%L %msg%n</pattern>
        </encoder>
        <target>System.out</target>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 控制台 WARN ERROR-->
    <appender name="consoleErrAppender" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss} [%thread] %.-5level]%C{0}:%L %msg%n</pattern>
        </encoder>
        <target>System.err</target>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARN</level>
        </filter>
    </appender>

    <!-- 关键:Root Logger 必须配置为DEBUG级别 如果配置INFO,那么consoleDebugAppender就被过滤掉了-->
    <root level="DEBUG">
        <appender-ref ref="consoleInfoAppender" />
        <appender-ref ref="consoleDebugAppender" />
        <appender-ref ref="consoleErrAppender" />
    </root>
</configuration>
Java 代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogbackExample {
    // 获取Logger实例
    private static final Logger logger = LoggerFactory.getLogger(LogbackExample.class);
    
    public static void main(String[] args) {
        logger.info("应用程序启动...");
        
        try {
            processData("测试数据");
            simulateError();
        } catch (Exception e) {
            logger.error("处理过程中发生错误", e);
        }
        
        logger.debug("这是一条调试信息");
        logger.warn("这是一条警告信息");
        logger.info("应用程序结束");
    }
    
    private static void processData(String data) {
        logger.info("开始处理数据: {}", data);  // 使用占位符,避免字符串拼接
        // 模拟业务逻辑
        logger.debug("数据处理中...");
    }
    
    private static void simulateError() {
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            logger.error("除零错误发生", e);
            throw new RuntimeException("计算失败", e);
        }
    }
}

SLF4J(门面) + Log4j 2(实现)

项目依赖 (Maven)
<dependencies>
    <!-- SLF4J 门面 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.9</version>
    </dependency>
    <!-- Log4j2 的 SLF4J 绑定器 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j2-impl</artifactId>
        <version>2.22.0</version>
    </dependency>
    <!-- Log4j2 核心 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.22.0</version>
    </dependency>
</dependencies>
配置文件:src/main/resources/log4j2.xml

默认查找顺序

  1. 系统属性指定(优先级最高):-Dlog4j.configurationFile=path/to/log4j2.xml

  2. 类路径中的配置文件(按优先级从高到低):

    1. log4j2-test.properties - 测试环境 properties 格式
    2. log4j2-test.yamllog4j2-test.yml - 测试环境 YAML 格式
    3. log4j2-test.jsonlog4j2-test.jsn - 测试环境 JSON 格式
    4. log4j2-test.xml - 测试环境 XML 格式
    5. log4j2.properties - 生产环境 properties 格式
    6. log4j2.yamllog4j2.yml - 生产环境 YAML 格式
    7. log4j2.jsonlog4j2.jsn - 生产环境 JSON 格式
    8. log4j2.xml - 生产环境 XML 格式(你提到的这个)
  3. 使用 默认配置 - 输出到控制台的简单配置

    • 主要类org.apache.logging.log4j.core.config.DefaultConfiguration
<?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} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>

        <!-- 文件输出 -->
        <RollingFile name="File" fileName="logs/app.log"
                     filePattern="logs/app-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
            <Policies>
                <TimeBasedTriggeringPolicy />
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>

        <!-- 异步日志Appender -->
        <Async name="AsyncFile">
            <AppenderRef ref="File"/>
        </Async>
    </Appenders>

    <Loggers>
        <!-- 特定包日志配置 -->
        <Logger name="com.example.service" level="info" additivity="false">
            <AppenderRef ref="Console"/>
        </Logger>

        <!-- 根日志配置 -->
        <Root level="debug">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="AsyncFile"/>
        </Root>
    </Loggers>
</Configuration>
Java 代码
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log4j2Example {
    // 获取Logger实例 - 代码与Logback完全相同!
    private static final Logger logger = LoggerFactory.getLogger(Log4j2Example.class);
    
    public static void main(String[] args) {
        logger.info("应用程序启动 - 使用Log4j2后端");
        
        demonstrateLogging();
        
        logger.info("应用程序结束");
    }
    
    private static void demonstrateLogging() {
        // 使用参数化日志,避免不必要的字符串拼接
        String user = "张三";
        int age = 25;
        logger.info("用户信息: 姓名={}, 年龄={}", user, age);
        
        // 不同日志级别
        logger.trace("这是一条TRACE级别日志");
        logger.debug("这是一条DEBUG级别日志");
        logger.warn("这是一条WARN级别日志");
        
        // 记录异常
        try {
            performRiskyOperation();
        } catch (IllegalStateException e) {
            logger.error("执行危险操作时发生错误, 用户={}", user, e);
        }
        
        // 性能监控示例
        long startTime = System.currentTimeMillis();
        try {
            Thread.sleep(100); // 模拟业务操作
            logger.debug("业务操作执行完成");
        } catch (InterruptedException e) {
            logger.warn("操作被中断", e);
            Thread.currentThread().interrupt();
        }
        long endTime = System.currentTimeMillis();
        logger.info("操作执行时间: {}ms", (endTime - startTime));
    }
    
    private static void performRiskyOperation() {
        logger.debug("开始执行危险操作");
        if (Math.random() > 0.5) {
            throw new IllegalStateException("随机失败模拟");
        }
        logger.debug("危险操作执行成功");
    }
}

关键对比总结

方面 SLF4J + Logback SLF4J + Log4j 2
依赖数量 2个 (slf4j-api + logback-classic) 3个 (slf4j-api + log4j-slf4j2-impl + log4j-core)
配置语法 Logback自有语法 Log4j2 XML语法
代码层面 完全一致 (都使用SLF4J API) 完全一致 (都使用SLF4J API)
异步日志 需要额外配置 原生支持,配置简单
性能特点 优秀 极佳,特别是在异步模式下

重要提示

  1. 代码完全兼容:两种实现的Java代码完全一样,这正是使用日志门面的价值所在!
  2. 切换简单:【SLF4J + Logback】和【SLF4J + Log4j 2】相互切换,只需更改依赖和xml配置文件,无需修改任何Java代码
  3. 配置文件位置:确保配置文件放在 src/main/resources/ 目录下
  4. 依赖冲突:注意不要同时引入多个绑定器(如同时有logback-classic和log4j-slf4j2-impl)

两种组合都是生产环境中的优秀选择,根据具体需求(性能要求、功能特性、团队熟悉度)来选择即可。

日志文件详细解析

Logback日志配置详解_logback 配置文件-CSDN博客

  • 再不懂得问deepseek!
posted @ 2025-11-10 13:37  deyang  阅读(1)  评论(0)    收藏  举报