Log4j2 实战指南:Java 日志框架

Log4j2 中文实战指南:log4j-api/log4j-core 依赖、log4j2.xml 配置结构(Appenders/Loggers)、PatternLayout 占位符、JsonLayout/JsonTemplateLayout JSON 输出、异步 Logger 与 garbage-free 性能模式、ThreadContext 上下文注入,以及观测云 DataKit 采集落地方案。

最佳实践
Log4j2 实战指南:Java 日志框架技术指南封面

Log4j2 是 Apache 对 Log4j 1 的全面重写,模块化架构、LMAX Disruptor 异步、garbage-free 模式,是 Java 生态常用的日志框架。本文从依赖配置讲到生产调优,并给出观测云平台的接入方案。

核心要点速览

  • 两个依赖起步:log4j-api(API)+ log4j-core(实现);业务代码建议面向 SLF4J 门面。
  • log4j2.xml 两段式:Appenders 定义去向与格式,Loggers 定义级别与路由。
  • 性能双武器:全异步 Logger(Disruptor)+ garbage-free 模式,性能收益需结合实际负载测试。
  • 采集验证:检查 JsonTemplateLayout 的时间、级别、异常和 ThreadContext 字段能否被后端正确读取。

依赖与最小配置

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-api</artifactId>
  <version>${log4j.version}</version>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>${log4j.version}</version>
</dependency>
<!-- 业务代码面向 SLF4J:再加 log4j-slf4j2-impl 桥接 -->

将 log4j.version 定义为与应用运行时兼容、仍受支持的具体版本,或使用 Log4j BOM 对齐模块。不要把旧教程的固定版本当作长期安全更新策略。

src/main/resources/log4j2.xml:

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

代码:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

private static final Logger log = LogManager.getLogger(OrderService.class);

log.info("订单创建成功 orderId={}", order.getId());
log.error("扣款失败", ex);   // 异常作为参数,自动带堆栈

级别:TRACE → DEBUG → INFO → WARN → ERROR → FATAL。

PatternLayout 常用占位符

占位符 含义
%d{ISO8601} Log4j 命名的 ISO8601 时间格式;默认不含时区偏移,跨主机采集需明确时区
%-5level 级别
%logger{36} logger 名(最多保留末尾 36 个名称组件,不是 36 字符)
%msg / %m 消息
%ex / %throwable 异常堆栈
%X{key} ThreadContext(MDC)值

JSON 输出:JsonLayout 与 JsonTemplateLayout

JsonLayout 需要对应的 Jackson 依赖;JsonTemplateLayout 则需要与 Log4j 版本匹配的 log4j-layout-template-json,用模板文件精确控制输出结构。替换前面 Console appender 时保留名称,使 Root 中的引用仍能找到它:

<Console name="Console" target="SYSTEM_OUT">
  <JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json"/>
</Console>

内置的 EcsLayout.json 模板直接输出 Elastic Common Schema 规范字段(@timestamp、log.level、log.logger);也可复制模板改名定制——比如把 @timestamp 改成 time、log.level 改成 status,对齐团队字段契约。

上下文注入:ThreadContext

import org.apache.logging.log4j.CloseableThreadContext;

try (CloseableThreadContext.Instance ignored = CloseableThreadContext
        .put("trace_id", traceId)
        .put("user_id", userId)) {
    log.info("订单处理完成");
}

片段中的 traceId、userId 和 log 由应用提供。作用域关闭时恢复之前的字段值,避免 clearAll() 一并清掉外层调用设置的上下文。

ThreadContext 中的键值需由布局或模板显式输出,JSON 模板中可直接引用。观测云的链路关联日志使用 trace_id 匹配;先检查模板是否输出该字段,以及它是否与对应请求的 Trace 一致。

性能调优:异步与 garbage-free

启用异步 Logger 前,先加入所选 Log4j 版本要求的兼容 LMAX Disruptor 依赖;这些属性须在日志系统初始化前设置。

# log4j2.component.properties 或系统属性
log4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
log4j2.enableThreadlocals=true
log4j2.garbagefreeThreadContextMap=true
  • 全异步 Logger:基于 LMAX Disruptor,后台线程处理主要写出工作,但业务线程仍有入队开销,队列满时还可能阻塞;选择性 <AsyncLogger> 与全异步 context selector 是两种配置方式,应按文档选择而非叠加;
  • Garbage-free:复用 ThreadLocal 缓冲区,能减少分配,具体效果受消息、布局、Appender 和 Web 应用 ThreadLocal 限制影响。

注意:异步模式崩溃时内存队列中的日志会丢——审计级日志保持同步,并验证刷盘和退出行为。采集器只能读取已输出的日志,不能恢复仍留在应用内存队列中的记录。

将 Log4j2 日志接入集中检索

主机上使用 RollingFile 输出时,按 DataKit 日志采集文档指定路径、source、service 和 Pipeline。JsonTemplateLayout 的 log.level 应映射为日志级别 status,@timestamp 按实际格式解析为事件时间;保留 logger 名、异常类型和请求标识用于过滤。

生成一条带异常堆栈和 trace_id 的测试日志,在观测云日志查看器核对字段与事件时间,再进入对应链路检查关联结果。最后测试轮转和应用退出,确认新日志仍被采集,避免仅凭正常运行时的一条记录判断接入完成。

常见问题(FAQ)

log4j2.xml 改了要重启吗?

<Configuration monitorInterval="30"> 开启定时检查,对支持监控的配置来源约每 30 秒检查变化,不保证修改后严格 30 秒内完成重载;需检查配置来源与重载结果。

Spring Boot 项目怎么换成 Log4j2?

排除 spring-boot-starter-logging(Logback),引入 spring-boot-starter-log4j2,放 log4j2.xml 即可。仅使用兼容 SLF4J API 的业务代码通常不用改,仍需核对 provider/桥接依赖,避免循环桥接与多个 provider。

RollingFile 的轮转策略怎么配?

<TimeBasedTriggeringPolicy interval="1"/> 的单位由 filePattern 日期精度决定;<SizeBasedTriggeringPolicy size="100MB"/> 按大小触发。<DefaultRolloverStrategy max="30"/> 控制索引范围,并非所有日期下总共只留 30 个文件,跨日期清理应配 Delete 条件。容器 stdout 文件通常由运行时管理轮转,采集器并不必然负责。

日志里 %X{} 是空的?

ThreadContext 的 put 必须发生在日志调用之前且同一线程。异步线程池需在提交时捕获所需上下文、在工作线程安装,并在任务结束后恢复该线程原有状态;TaskDecorator 等封装也需要实现这些步骤,不能只添加一个上下文数据提供器就假定传播已完成。

系列阅读


获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台