【Java】日志框架

一、概述

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)),避免字符串拼接开销。
  • 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属性)。
      • 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 日志时触发邮件告警。

十、资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值