> 技术文档 > 会log4j日志打印?但是log4j架构原理和适配你了解嘛?

会log4j日志打印?但是log4j架构原理和适配你了解嘛?


作者:唐叔在学习
专栏:唐叔的Java实践
关键词:#Log4j #敏感日志处理 #日志异步输出 #多日志框架统一 #日志格式化

Hello,大家好!我是唐叔,今天给大家带来的是有关Log4j的使用介绍,希望读完本文章能加深你对Log4j的理解。

文章目录

    • 一、Log4j介绍
    • 二、Log4j基本使用
      • 2.1 引入组件
      • 2.2 编写配置文件
      • 2.3 使用组件
    • 三、Log4j进阶使用
      • 3.1 Log4j日志信息增强
        • 1)日志级别
        • 2)日志格式
          • 补充内容:环境差异化配置
      • 3.2 Log4j日志结构化
      • 3.3 Log4j日志归档
        • 补充内容:日志异步
      • 3.4 Log4j日志过滤
        • 补充内容:敏感字打印替换
    • 四、Log4j深入理解
      • 4.1 Log4j2 架构&配置文件解读
        • 4.1.1 Log4j 整体架构概述
        • 4.1.2 运作机理概述
        • 4.1.3 Log4j2.xml理解
      • 4.2 多日志框架混用的统一策略

一、Log4j介绍

Log4j 是一款功能强大的工业级 Java 日志框架,能够高效处理各类日志记录任务,使开发者可以更专注于业务逻辑的实现。

会log4j日志打印?但是log4j架构原理和适配你了解嘛?

其主要功能包括:

  • 丰富日志内容:自动附加时间戳、文件名、类与方法名称、行号、主机信息、日志级别等上下文信息;

  • 灵活格式化消息:支持通过预定义的布局(如 CSV、JSON 等)对日志消息进行结构化格式化;

  • 多目标输出:可以将日志写入多种终端,包括控制台、文件、套接字、数据库及消息队列等;

  • 精细化过滤:可根据严重级别、内容关键字等条件过滤待输出的日志,提升有效信息密度。

二、Log4j基本使用

使用log4j,就三步骤:引入组件、撰写配置文件、使用组件。

2.1 引入组件

<dependencies>  <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.17.1</version>  </dependency>  <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.17.1</version> </dependency></dependencies>

PS:使用log4j,需要同时引入API和对应的实现模块。

2.2 编写配置文件

<Configuration status=\"WARN\"> <Properties> <Property name=\"LOG_PATTERN\">%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Property> </Properties> <Appenders>  <Console name=\"Console\" target=\"SYSTEM_OUT\"> <PatternLayout pattern=\"${LOG_PATTERN}\"/> </Console> </Appenders> <Loggers> <Root level=\"info\"> <AppenderRef ref=\"Console\"/> </Root> </Loggers></Configuration>

PS:配置文件的命名必须是以log4j2为前缀,如log4j2.xml、log4j2-test.xml等,且需要放在classpath根目录下(通常是src/main/resources/

2.3 使用组件

package cn.uil.demo;import org.apache.logging.log4j.LogManager;import org.apache.logging.log4j.Logger;public class MyClass { // 获取Logger实例 private static final Logger logger = LogManager.getLogger(MyClass.class); public void myMethod() { // 记录不同级别的日志 logger.trace(\"这是一条TRACE级别的日志\"); logger.debug(\"这是一条DEBUG级别的日志\"); logger.info(\"这是一条INFO级别的日志\"); logger.warn(\"这是一条WARN级别的日志\"); logger.error(\"这是一条ERROR级别的日志\"); logger.fatal(\"这是一条FATAL级别的日志\"); // 带参数的日志 String name = \"张三\"; int age = 25; logger.info(\"用户信息:姓名={}, 年龄={}\", name, age); // 异常日志记录 try { // 可能抛出异常的代码 int result = 10 / 0; } catch (Exception e) { logger.error(\"计算过程中发生错误\", e); } } public static void main(String[] args) { MyClass myClass = new MyClass(); myClass.myMethod(); }}

日志输出如下:

2025-08-26 08:37:16.322 [main] INFO cn.uil.demo.MyClass - 这是一条INFO级别的日志2025-08-26 08:37:16.324 [main] WARN cn.uil.demo.MyClass - 这是一条WARN级别的日志2025-08-26 08:37:16.324 [main] ERROR cn.uil.demo.MyClass - 这是一条ERROR级别的日志2025-08-26 08:37:16.324 [main] FATAL cn.uil.demo.MyClass - 这是一条FATAL级别的日志2025-08-26 08:37:16.325 [main] INFO cn.uil.demo.MyClass - 用户信息:姓名=张三, 年龄=252025-08-26 08:37:16.325 [main] ERROR cn.uil.demo.MyClass - 计算过程中发生错误java.lang.ArithmeticException: / by zero at cn.uil.demo.MyClass.myMethod(MyClass.java:27) [classes/:?] at cn.uil.demo.MyClass.main(MyClass.java:35) [classes/:?]

三、Log4j进阶使用

如果你只是想对 log4j 有基本的了解,上述内容足以。如果想要进一步,可以继续往下阅读。

3.1 Log4j日志信息增强

和使用 System.out.print(\"log data\") 相比,使用 Log4j 比较直接的差别莫过于能获取到更多信息,比如日志打印时间记录、日志分级等。

1)日志级别

日志级别是预定义的枚举值,用于表示一条日志信息的重要性或严重性。它们有两个关键特性:

  1. 有序性:级别有高低之分,顺序是固定的。
  2. 门槛性:Logger 只会输出不低于其当前设置级别的日志信息。这条规则是理解日志级别的重中之重。

Log4j 2 提供了以下标准级别,其顺序和典型用途如下:

级别 数值 含义与用途 示例 生产环境建议 ALL Integer.MIN_VALUE 最低级别,打开所有日志记录。主要用于配置,而不是在代码中使用。 关闭 TRACE 600 追踪信息。比 DEBUG 更细致、更冗长,用于追踪复杂的程序流,如每个循环内的状态。 TRACE - Entering method calculate(), loop i=1, value=5 关闭 DEBUG 500 调试信息。详细的系统运行信息,对调试应用程序非常有帮助,如输入参数、中间结果、变量值。 DEBUG - Parameters: userId=123, action=login 通常关闭 INFO 400 信息性消息。记录应用程序正常运行的关键业务节点和状态。用于回答“系统正在做什么”。 INFO - User [admin] logged in successfully. INFO - Order [1001] has been shipped. 打开 WARN 300 警告。表明发生了不常见非错误的情况。应用程序仍在正常运行,但未来可能引发错误,需要关注。 WARN - Cache size is approaching the limit (90%). WARN - API response was slower than expected (2000ms). 打开 ERROR 200 错误。表明发生了错误事件,影响了当前操作或请求(如保存失败、连接断开),但应用程序整体可能仍在运行。需要尽快调查修复。 ERROR - Failed to connect to database. Retrying... ERROR - Payment processing failed for order [1001]. 打开 FATAL 100 致命错误。表明发生了非常严重的错误,可能导致应用程序完全崩溃或无法继续运行。 FATAL - Critical configuration file is missing. Shutting down. FATAL - JVM is running out of memory. 打开 OFF Integer.MAX_VALUE 最高级别,关闭所有日志记录。主要用于配置。 关闭

级别高低顺序ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF

使用示例说明:

假设我们在配置文件中将 Root Logger 的级别设置为 WARN

<Root level=\"WARN\"> <AppenderRef ref=\"Console\"/></Root>

那么在代码中调用不同方法时,会发生以下情况:

logger.trace(\"This is a trace message.\"); // TRACE(600)  **不输出**logger.debug(\"This is a debug message.\"); // DEBUG(500)  **不输出**logger.info(\"This is an info message.\"); // INFO(400)  **不输出**logger.warn(\"This is a warning!\"); // WARN(300) == WARN(300) -> **输出**logger.error(\"This is an error!\"); // ERROR(200) > WARN(300) -> **输出**logger.fatal(\"This is a fatal error!\"); // FATAL(100) > WARN(300) -> **输出**

在这里插入图片描述

环境配置建议:

环境 推荐级别 原因 本地开发 DEBUG 需要最详细的信息来调试和开发功能。 测试环境 DEBUG / INFO 需要详细信息来排查测试中发现的问题。 生产环境 INFOWARN 平衡可观察性和性能。INFO 记录关键业务流程;WARN 只记录警告和错误,日志量最小,性能最好。 线上排查问题 动态调整为 DEBUG 当生产环境出现疑难问题时,可以通过管理工具(如Spring Boot Actuator)临时将特定Logger的级别调为DEBUG来抓取详细信息,排查完后改回。
2)日志格式

Log4j 的核心功能是将日志信息按照用户指定的格式输出到指定的目的地(控制台、文件等)。这个格式就是通过 布局(Layout) 来定义的,其中最常用、最灵活的是 PatternLayout

PatternLayout 允许通过一个“转换模式”字符串(即 pattern)来定义日志输出的格式。

转换说明符:以 % 开头,用于插入特定的日志事件数据,如日期、日志级别、类名等。格式通常为 %{格式}{参数},例如 %d{HH:mm:ss.SSS}, %logger{36}

以下是一些最常用和重要的转换说明符:

说明符 含义与作用 示例输出 %d{pattern} 日期/时间{pattern} 指定日期格式,遵循 Java SimpleDateFormat 规则。 %d{yyyy-MM-dd HH:mm:ss.SSS} -> 2023-10-27 14:35:21.123 %p / %level 日志级别。输出日志事件的级别。 DEBUG, INFO, WARN, ERROR %t 线程名。输出生成日志事件的线程名称。 main, http-nio-8080-exec-1 %c / %logger Logger 名称。通常是发出日志请求的类名。{precision} 可简化包名。 %c{1} 输出类名 (Main),%c 输出全限定类名 (com.example.Main) %M 方法名。输出发出日志请求的方法名。注意:获取方法名有性能开销,生产环境慎用。 doGet, main %L 行号。输出发出日志请求的代码行号。同样有性能开销,生产环境慎用。 123 %m / %msg / %message 应用程序消息。输出应用程序提供的日志内容。 User login successfully. %n 平台相关的换行符。在 Windows 上是 \\r\\n,在 Unix/Linux 上是 \\n%X 输出和当前线程关联的 MDC (Mapped Diagnostic Context) 内容%X{key} 输出指定 key 的值。 %X{requestId} -> rId-12345 %throwable 输出与日志事件关联的异常堆栈跟踪。如果不存在异常,则不输出任何内容。 java.io.FileNotFoundException: ... %highlight 高亮显示。通常与其他模式组合,根据日志级别为内容着色。需要支持 ANSI 颜色的终端。 %highlight{%p} 会让 ERROR 显示为红色 %r 相对时间。输出从应用程序启动到日志事件创建所经过的毫秒数。 15234 (毫秒) %F 文件名。输出发出日志请求的源代码文件名。 Main.java

常用格式示例

  1. 标准开发/调试格式:这种格式信息非常全,便于调试,但因为包含了方法名(%M)和行号(%L),性能有损耗,不建议在生产环境使用。
%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}.%M(%L) - %msg%n

说明: %-5level: -5 表示左对齐并固定宽度为5个字符,这样各级别(INFO, DEBUG, ERROR)就能对齐,更美观。

  1. 生产环境格式:生产环境通常更关心上下文(时间、级别、线程、Logger名)和业务消息,避免使用高开销的%M%L
%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
  1. 高亮模式:在 IntelliJ IDEA 或 Terminal 等支持 ANSI 颜色的控制台中,使用 %highlight 可以让日志级别更加醒目。
%highlight{%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n}{FATAL=red blink, ERROR=red, WARN=yellow, INFO=green, DEBUG=cyan, TRACE=white}

在这里插入图片描述

这里以测试环境模式为示例,高亮模式貌似社区版IDEA不支持…

补充内容:环境差异化配置

核心机制: Log4j 2 在启动时,会按照一个明确的顺序查找配置文件。它允许在文件名中包含环境变量系统属性,从而实现环境的自动切换。常见的环境差异化配置有:log4j2-dev.xml (开发环境) 、log4j2-test.xml (测试环境)等。

  • 通过 JVM 参数指定环境

这种方式通过在启动应用时传递一个 JVM 参数来明确指定要使用哪个环境的配置。

  1. 创建不同环境的配置文件
    在你的项目资源目录(如 src/main/resources)下创建多个配置文件:

    • log4j2-dev.xml - 开发环境配置(级别为 DEBUG, 输出到控制台)[代码略]
    • log4j2-pro.xml - 生产环境配置(级别为 INFO, 输出到文件和时间滚动归档)[代码略]
  2. 在启动命令中指定环境
    通过 JVM 参数 -Dlog4j.configurationFile 来指定激活哪个文件。参数的值就是配置文件的完整名称

    • 在 IDEA 中启动(开发):

      • 编辑运行配置(Edit Configurations…)

      • VM options 中添加:-Dlog4j.configurationFile=log4j2-dev.xml

    • 在服务器上启动java -Dlog4j.configurationFile=log4j2-pro.xml -jar myapplication.jar

在这里插入图片描述

在 IDEA 中没显示 VM options,可以点击 Modify options,勾选 Add VM options 即可。

3.2 Log4j日志结构化

Log4j日志结构化,主要依赖于 JSON Template Layout。它允许你使用 Jackson JSON 处理器和 JSON 模板,将日志事件(LogEvent)高度定制化地序列化为 JSON 对象,而不是一行行的文本。与传统 PatternLayout 相比,结构化数据 (Structured Data),日志被输出为键值对(JSON),便于日志收集系统(如ELK)直接摄取和索引,无需复杂的解析(Grok)规则。

使用方式

  • 添加依赖
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-layout-template-json</artifactId> <version>2.17.1</version> </dependency>
  • 配置 (log4j2.xml)
<Appenders> <Console name=\"Console\" target=\"SYSTEM_OUT\"> <JsonTemplateLayout eventTemplateUri=\"classpath:LogstashJsonEventLayoutV1.json\"/> </Console></Appenders>

LogstashJsonEventLayoutV1.json 是内置的json格式模板,也可以配置自定义json模板,然后这里进行替换即可。

  • 使用效果演示

在这里插入图片描述

3.3 Log4j日志归档

Log4j 通过 Appender 组件模型来实现将日志输出到控制台、文件、套接字、数据库、队列等各种位置。

以将日志同时输出到控制台和文件为例,调整 log4j2.xml 文件,增加RollingFile配置。

<RollingFile name=\"RollingFile\" fileName=\"logs/app.log\" filePattern=\"logs/app-%d{yyyy-MM-dd}-%i.log.gz\"> <PatternLayout pattern=\"%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n\"/> <Policies>  <TimeBasedTriggeringPolicy interval=\"1\" modulate=\"true\"/>  <SizeBasedTriggeringPolicy size=\"10 MB\"/> </Policies>  <DefaultRolloverStrategy max=\"15\"> <Delete basePath=\"logs\" maxDepth=\"1\"> <IfFileName glob=\"app-*.log.gz\"/> <IfLastModified age=\"7D\"/> </Delete> </DefaultRolloverStrategy></RollingFile>

效果演示

在这里插入图片描述

日志也可以写入队列等,额外引入日志扩展组件即可,这里就不作展开了。

补充内容:日志异步

Log4j 使用 Async 组件实现日志异步打印。

以日志异步输出到文件为例,调整 log4j2.xml 文件,在 Appenders 添加 Async 组件。

<Async name=\"AsyncFile\" bufferSize=\"8192\">  <AppenderRef ref=\"RollingFile\"/>  <!--  --></Async>

效果演示

在这里插入图片描述

3.4 Log4j日志过滤

Log4j 的 过滤器(Filter) 组件可以在日志事件进入 Appender 或 Logger 之前对其进行拦截和判断,决定是接受、拒绝还是忽略该日志事件。例如可以使用 RegexFilter 用于过滤敏感信息,不做日志打印。

<RegexFilter regex=\".*password.*\" onMatch=\"DENY\" onMismatch=\"NEUTRAL\" useRawMsg=\"true\"/>

效果演示

在这里插入图片描述

补充内容:敏感字打印替换

对于复杂的数据脱敏(如过滤JSON字符串中的特定字段),RewriteAppender 允许你在将日志事件传递给最终Appender之前,先对其进行修改。

  • 自定义敏感信息替换类
// 使用 @Plugin 注解注册插件@Plugin(name = \"RegexReplaceRewritePolicy\", category = Core.CATEGORY_NAME, elementType = \"rewritePolicy\", printObject = true)public final class RegexReplaceRewritePolicy implements RewritePolicy { private final Pattern regexPattern; private final String replacement; // 构造函数 private RegexReplaceRewritePolicy(String regex, String replacement) { this.regexPattern = Pattern.compile(regex); this.replacement = replacement; } // 重写 rewrite 方法,实现正则替换逻辑 @Override public LogEvent rewrite(LogEvent event) { // 获取原始日志消息 String originalMessage = event.getMessage().getFormattedMessage(); // 执行正则替换 String modifiedMessage = regexPattern.matcher(originalMessage).replaceAll(replacement); // 如果消息未被修改,直接返回原事件 if (originalMessage.equals(modifiedMessage)) { return event; } // 创建并返回一个新的 LogEvent,包含修改后的消息 return new Log4jLogEvent.Builder(event) .setMessage(new SimpleMessage(modifiedMessage)) .build(); } // 使用 @PluginFactory 注解标记工厂方法,用于从配置创建策略实例 @PluginFactory public static RegexReplaceRewritePolicy createPolicy( @PluginAttribute(\"regex\") String regex, @PluginAttribute(\"replacement\") String replacement) { return new RegexReplaceRewritePolicy(regex, replacement); }}
  • 配置log4j2.xml 文件:在Appenders中声明
<Configuration status=\"WARN\"> <Appenders>  <Console name=\"Console\" target=\"SYSTEM_OUT\"> <PatternLayout pattern=\"%d{HH:mm:ss.SSS} [%t] %-5level - %msg%n\"/> </Console>  <Rewrite name=\"RewriteSensitiveData\">  <AppenderRef ref=\"Console\"/>  <RegexReplaceRewritePolicy regex=\"(?i)(password|pwd)=[^&\\s]+\" replacement=\"$1=***\"/> </Rewrite> </Appenders> <Loggers> <Root level=\"INFO\">  <AppenderRef ref=\"RewriteSensitiveData\"/> </Root> </Loggers></Configuration>rite>

效果演示

在这里插入图片描述

四、Log4j深入理解

下面的内容是唐叔对 Log4j 的一些理解,如果你只是掌握 Log4j 的使用,下述内容就可以直接略过啦。

4.1 Log4j2 架构&配置文件解读

通过上述的配置,其实大家可以明确地体会到 Log4j.xml 文件在 Log4j 的使用中十分重要。

在理解 Log4j.xml 配置文件前,我们先了解下 Log4j 的代码架构。下图是 Log4j 官网的架构图。

在这里插入图片描述

4.1.1 Log4j 整体架构概述

Log4j 的架构主要由以下几个核心部分组成:

  1. LoggerContext:日志系统的入口和上下文环境。

  2. Configuration:配置信息的容器,包括 Appender、LoggerConfig、Filter 等。

  3. Logger:用户直接使用的日志记录接口。

  4. LoggerConfig:配置每个 Logger 的行为(如级别、Appender、Filter)。

  5. Appender:定义日志输出的目的地(如文件、控制台、网络)。

  6. Layout:定义日志输出的格式。

  7. Filter:在不同级别上过滤日志事件。

  8. StrSubstitutor / Interpolator / StrLookup:处理配置中的变量替换(如 ${env:USER})。

4.1.2 运作机理概述
  1. 用户代码调用 Logger:
    logger.info(\"This is a log message\");

  2. Logger 委托给 LoggerConfig:

    • Logger 本身不处理日志,而是委托给其对应的 LoggerConfig。

    • LoggerConfig 根据配置决定是否处理该日志(基于 Level 和 Filter)。

  3. LoggerConfig 调用 AppenderControl:

    • 每个 LoggerConfig 包含一个或多个 AppenderControl。

    • AppenderControl 是对 Appender 的封装,可能附加 Filter。

  4. Appender 输出日志:

    • Appender 使用 Layout 格式化日志事件。

    • 最终输出到目的地(文件、控制台、数据库等)。

  5. Filter 机制:

    • Filter 可以在四个级别上设置:Configuration、LoggerConfig、AppenderControl、Appender。

    • 每个 Filter 决定是否接受、拒绝或中立处理 LogEvent。

  6. 变量替换:

    • 使用 StrSubstitutorInterpolatorStrLookup 链解析 ${...} 表达式。
4.1.3 Log4j2.xml理解

当用户调用 Logger 时,Log4j 会读取配置文件信息,根据配置文件来判断如何处理用户的日志打印。

而每个配置文件上的每个标签都有具体的含义,下图是简要的解读:

在这里插入图片描述

简单说,Log4j 通过配置驱动责任链模式实现了高度可扩展的日志系统。用户只需与 Logger 交互,而底层的 LoggerConfigAppenderFilterLayout 等组件通过配置灵活组合,满足各种日志需求。

4.2 多日志框架混用的统一策略

事实上,Java主流的日志框架,除了 log4j,其实还有 logback、Slf4j等。而大型项目是由很多组件构成,每个组件可能最终使用的日志框架不尽一样。而 Log4j 的开发者其实也考虑到了这一点,利用桥接模式巧妙的解决了多日志框架混用的日志输出混乱问题。下图是 Log4j 官网的多日志框架桥接适配方式图。

在这里插入图片描述

项目本身或存在多个模块或组件(Application、Library 1/2/3),使用了不同的日志框架。默认用户调用的都是各个日志框架的API层(SLF4J、JUL、JPL、Log4j API),通过桥接器的方式(SLF4J-to-Log4jJUL-to-Log4jJPL-to-Log4j),将其他日志API的调用转发到Log4j API的实现层 Log4j Core,而最终都统一采用 Log4j 的方式进行日志打印输出。

理解了上面的原理,那么日常该如何使用呢,下面是唐叔的一些建议:

  • 如果你的项目只使用 Log4j2,直接引入 log4j-core (API实现)和 log4j-api

  • 如果项目中有第三方库使用 SLF4J,引入:

    • log4j-core

    • log4j-api

    • log4j-slf4j-impl(即图中的 SLF4J-to-Log4j

    • 如果项目中存在 SLF4J API的实现,需要同步移除

  • 如果使用 JUL,可引入 log4j-jul 桥接器。

  • 注意:避免同时引入多个桥接器或日志实现,防止冲突。


好啦,以上就是今天的讲解内容啦,感谢阅读。