1. 概述

日志记录是软件开发中的关键环节。在 Java 生态中,Log4J 凭借其灵活性和简洁性,数十年来一直是最流行的日志框架。

Log4j 2 是经典 Log4j 框架的全新升级版本。本文将通过实际示例,介绍最常用的 Appender、Layout 和 Filter。

在 Log4J2 中:

  • Appender 是日志事件的输出目标,可以是简单的控制台,也可以是复杂的 RDBMS
  • Layout 决定日志的呈现格式
  • Filter 根据各种条件过滤日志数据

2. 环境准备

为了理解不同日志组件及其配置,我们将设置多个测试用例,每个用例包含一个 log4J2.xml 配置文件和一个 JUnit 4 测试类。

所有示例共需两个 Maven 依赖:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.19.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.19.0</version>
    <type>test-jar</type>
    <scope>test</scope>
</dependency>

除了主要的 log4j-core 包,我们还需要包含其测试 jar,以访问测试非常规配置文件所需的上下文规则。

3. 默认配置

ConsoleAppenderLog4J 2 核心包的默认配置。它按简单模式将日志输出到系统控制台:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="ConsoleAppender" target="SYSTEM_OUT">
            <PatternLayout 
              pattern="%d [%t] %-5level %logger{36} - %msg%n%throwable"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="ERROR">
            <AppenderRef ref="ConsoleAppender"/>
        </Root>
    </Loggers>
</Configuration>

XML 配置关键标签解析:

  • Configuration:Log4J2 配置文件的根元素,status 属性控制内部 Log4J 事件的日志级别
  • Appenders:包含一个或多个 Appender,此处配置输出到标准输出的控制台 Appender
  • Loggers:可包含多个 Logger 元素。特殊的 Root 标签配置无名标准日志记录器,接收应用所有日志消息。每个日志记录器可设置最小日志级别
  • AppenderRef:定义对 Appenders 部分元素的引用,ref 属性链接到 Appender 的 name 属性

对应的单元测试同样简单:

@Test
public void givenLoggerWithDefaultConfig_whenLogToConsole_thanOK()
  throws Exception {
    Logger logger = LogManager.getLogger(getClass());
    Exception e = new RuntimeException("This is only a test!");

    logger.info("This is a simple message at INFO level. " +
      "It will be hidden.");
    logger.error("This is a simple message at ERROR level. " +
    "This is the minimum visible level.", e);
}

4. 带自定义模式的 ConsoleAppender

我们在独立 XML 文件中定义带自定义颜色模式的控制台 Appender,并将其包含到主配置中:

<?xml version="1.0" encoding="UTF-8"?>
<Console name="ConsoleAppender" target="SYSTEM_OUT">
    <PatternLayout pattern="%style{%date{DEFAULT}}{yellow}
      %highlight{%-5level}{FATAL=bg_red, ERROR=red, WARN=yellow, INFO=green} 
      %message"/>
</Console>

此文件使用运行时由 Log4J2 替换的模式变量:

  • %style{...}{colorname}:将括号内文本以指定颜色输出
  • %highlight{...}{FATAL=colorname, ...}:类似 style,但可为每个日志级别指定不同颜色
  • %date{format}:替换为指定格式的当前日期,此处使用默认格式 yyyy-MM-dd HH:mm:ss,SSS
  • %-5level:右对齐打印日志级别
  • %message:原始日志消息

主配置中引入该 Appender:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" xmlns:xi="http://www.w3.org/2001/XInclude">
    <Appenders>
        <xi:include href="log4j2-includes/
          console-appender_pattern-layout_colored.xml"/>
    </Appenders>
    <Loggers>
        <Root level="DEBUG">
            <AppenderRef ref="ConsoleAppender"/>
        </Root>
    </Loggers>
</Configuration>

单元测试:

@Test
public void givenLoggerWithConsoleConfig_whenLogToConsoleInColors_thanOK() 
  throws Exception {
    Logger logger = LogManager.getLogger("CONSOLE_PATTERN_APPENDER_MARKER");
    logger.trace("This is a colored message at TRACE level.");
    ...
}

5. 异步文件 Appender:JSONLayout + BurstFilter

有时需要异步写入日志(例如应用性能比日志可用性更重要)。此时可用 AsyncAppender

示例配置异步 JSON 日志文件,并添加限制输出速率的突发过滤器:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        ...
        <File name="JSONLogfileAppender" fileName="target/logfile.json">
            <JSONLayout compact="true" eventEol="true"/>
            <BurstFilter level="INFO" rate="2" maxBurst="10"/>
        </File>
        <Async name="AsyncAppender" bufferSize="80">
            <AppenderRef ref="JSONLogfileAppender"/>
        </Async>
    </Appenders>
    <Loggers>
        ...
        <Logger name="ASYNC_JSON_FILE_APPENDER" level="INFO"
          additivity="false">
            <AppenderRef ref="AsyncAppender" />
        </Logger>
        <Root level="INFO">
            <AppenderRef ref="ConsoleAppender"/>
        </Root>
    </Loggers>
</Configuration>

关键配置说明:

  • JSONLayout 配置为每行一个日志事件
  • BurstFilter:当 INFO 及以上级别事件超过 2 个/秒时丢弃,最多丢弃 10 个
  • AsyncAppender 缓冲区大小为 80 条日志,缓冲区满后刷新到文件

单元测试通过循环填充缓冲区,检查日志文件行数:

@Test
public void givenLoggerWithAsyncConfig_whenLogToJsonFile_thanOK() 
  throws Exception {
    Logger logger = LogManager.getLogger("ASYNC_JSON_FILE_APPENDER");

    final int count = 88;
    for (int i = 0; i < count; i++) {
        logger.info("This is async JSON message #{} at INFO level.", count);
    }
    
    long logEventsCount 
      = Files.lines(Paths.get("target/logfile.json")).count();
    assertTrue(logEventsCount > 0 && logEventsCount <= count);
}

6. 滚动文件 Appender 与 XMLLayout

接下来创建滚动日志文件:文件达到指定大小时压缩并轮转。这次使用 XML 布局:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <RollingFile name="XMLRollingfileAppender"
          fileName="target/logfile.xml"
          filePattern="target/logfile-%d{yyyy-MM-dd}-%i.log.gz">
            <XMLLayout/>
            <Policies>
                <SizeBasedTriggeringPolicy size="17 kB"/>
            </Policies>
        </RollingFile>
    </Appenders>
    <Loggers>
        <Logger name="XML_ROLLING_FILE_APPENDER" 
       level="INFO" additivity="false">
            <AppenderRef ref="XMLRollingfileAppender" />
        </Logger>
        <Root level="TRACE">
            <AppenderRef ref="ConsoleAppender"/>
        </Root>
    </Loggers>
</Configuration>

关键配置说明:

  • RollingFilefilePattern 属性定义轮转文件名,支持占位符(示例包含日期和计数器)
  • XMLLayout 默认配置写入无根元素的单个日志事件对象
  • 使用基于大小的策略轮转日志文件

单元测试类与上一节类似:

@Test
public void givenLoggerWithRollingFileConfig_whenLogToXMLFile_thanOK()
  throws Exception {
    Logger logger = LogManager.getLogger("XML_ROLLING_FILE_APPENDER");
    final int count = 88;
    for (int i = 0; i < count; i++) {
        logger.info(
          "This is rolling file XML message #{} at INFO level.", i);
    }
}

7. Syslog Appender

当需要通过网络将日志事件发送到远程机器时,最简单的方式是使用 Log4J2 的 Syslog Appender

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        ...
        <Syslog name="Syslog" 
          format="RFC5424" host="localhost" port="514" 
          protocol="TCP" facility="local3" connectTimeoutMillis="10000" 
          reconnectionDelayMillis="5000">
        </Syslog>
    </Appenders>
    <Loggers>
        ...
        <Logger name="FAIL_OVER_SYSLOG_APPENDER" 
          level="INFO" 
          additivity="false">
            <AppenderRef ref="FailoverAppender" />
        </Logger>
        <Root level="TRACE">
            <AppenderRef ref="Syslog" />
        </Root>
    </Loggers>
</Configuration>

Syslog 标签属性说明:

  • name:定义 Appender 名称(同一应用/配置中需唯一)
  • format:设置为 BSD 或 RFC5424,决定 Syslog 记录格式
  • host & port:远程 Syslog 服务器的主机名和端口
  • protocol:使用 TCP 或 UDP
  • facility:事件写入的 Syslog 设施
  • connectTimeoutMillis:等待建立连接的时间(默认 0)
  • reconnectionDelayMillis:重试连接前的等待时间

8. FailoverAppender

当主 Appender 处理日志事件失败时,为避免数据丢失,可使用 FailoverAppender

例如:当 Syslog Appender 无法发送事件到远程机器时,临时回退到 FileAppender

<Failover name="FailoverAppender" primary="Syslog">
    <Failovers>
        <AppenderRef ref="ConsoleAppender" />
    </Failovers>
</Failover>

测试代码:

@Test
public void givenLoggerWithFailoverConfig_whenLog_thanOK()
  throws Exception {
    Logger logger = LogManager.getLogger("FAIL_OVER_SYSLOG_APPENDER");
    Exception e = new RuntimeException("This is only a test!"); 

    logger.trace("This is a syslog message at TRACE level.");
    logger.debug("This is a syslog message at DEBUG level.");
    logger.info("This is a syslog message at INFO level. 
      This is the minimum visible level.");
    logger.warn("This is a syslog message at WARN level.");
    logger.error("This is a syslog message at ERROR level.", e);
    logger.fatal("This is a syslog message at FATAL level.");
}

9. JDBC Appender

JDBC Appender 通过标准 JDBC 将日志事件发送到 RDBMS,连接可通过 JNDI 数据源或连接工厂获取。

基础配置包含 DataSource/ConnectionFactoryColumnConfigstableName

<JDBC name="JDBCAppender" tableName="logs">
    <ConnectionFactory 
      class="com.baeldung.logging.log4j2.tests.jdbc.ConnectionFactory" 
      method="getConnection" />
    <Column name="when" isEventTimestamp="true" />
    <Column name="logger" pattern="%logger" />
    <Column name="level" pattern="%level" />
    <Column name="message" pattern="%message" />
    <Column name="throwable" pattern="%ex{full}" />
</JDBC>

测试代码:

@Test
public void givenLoggerWithJdbcConfig_whenLogToDataSource_thanOK()
  throws Exception {
    Logger logger = LogManager.getLogger("JDBC_APPENDER");
    final int count = 88;
    for (int i = 0; i < count; i++) {
        logger.info("This is JDBC message #{} at INFO level.", count);
    }

    Connection connection = ConnectionFactory.getConnection();
    ResultSet resultSet = connection.createStatement()
      .executeQuery("SELECT COUNT(*) AS ROW_COUNT FROM logs");
    int logCount = 0;
    if (resultSet.next()) {
        logCount = resultSet.getInt("ROW_COUNT");
    }
    assertTrue(logCount == count);
}

10. 总结

本文通过简单示例展示了如何在 Log4J2 中使用不同的日志 Appender、Filter 和 Layout,以及它们的配置方式。关键点总结:

核心组件关系

  • Appender 决定日志输出目标(控制台/文件/数据库等)
  • Layout 控制日志格式(文本/JSON/XML等)
  • Filter 实现日志过滤(级别/速率/内容等)

⚠️ 常见踩坑

  • 异步 Appender 需合理设置缓冲区大小
  • 滚动文件需注意 filePattern 占位符匹配
  • Failover 机制需测试主备切换场景

🚀 最佳实践

  • 生产环境推荐异步日志提升性能
  • 关键业务日志建议多 Appender 冗余
  • 复杂布局优先考虑结构化格式(JSON/XML)

原始标题:Intro to Log4j2 - Appenders, Layouts and Filters | Baeldung