会log4j日志打印?但是log4j架构原理和适配你了解嘛?
作者:唐叔在学习
专栏:唐叔的Java实践
关键词:#Log4j #敏感日志处理 #日志异步输出 #多日志框架统一 #日志格式化
Hello,大家好!我是唐叔,今天给大家带来的是有关Log4j的使用介绍,希望读完本文章能加深你对Log4j的理解。
文章目录
一、Log4j介绍
Log4j 是一款功能强大的工业级 Java 日志框架,能够高效处理各类日志记录任务,使开发者可以更专注于业务逻辑的实现。
其主要功能包括:
-
丰富日志内容:自动附加时间戳、文件名、类与方法名称、行号、主机信息、日志级别等上下文信息;
-
灵活格式化消息:支持通过预定义的布局(如 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)日志级别
日志级别是预定义的枚举值,用于表示一条日志信息的重要性或严重性。它们有两个关键特性:
- 有序性:级别有高低之分,顺序是固定的。
- 门槛性:Logger 只会输出不低于其当前设置级别的日志信息。这条规则是理解日志级别的重中之重。
Log4j 2 提供了以下标准级别,其顺序和典型用途如下:
Integer.MIN_VALUE
DEBUG
更细致、更冗长,用于追踪复杂的程序流,如每个循环内的状态。TRACE - Entering method calculate(), loop i=1, value=5
DEBUG - Parameters: userId=123, action=login
INFO - User [admin] logged in successfully.
INFO - Order [1001] has been shipped.
WARN - Cache size is approaching the limit (90%).
WARN - API response was slower than expected (2000ms).
ERROR - Failed to connect to database. Retrying...
ERROR - Payment processing failed for order [1001].
FATAL - Critical configuration file is missing. Shutting down.
FATAL - JVM is running out of memory.
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
INFO
或 WARN
INFO
记录关键业务流程;WARN
只记录警告和错误,日志量最小,性能最好。DEBUG
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
{precision}
可简化包名。%c{1}
输出类名 (Main
),%c
输出全限定类名 (com.example.Main
)%M
doGet
, main
%L
123
%m
/ %msg
/ %message
User login successfully.
%n
\\r\\n
,在 Unix/Linux 上是 \\n
。%X
%X{key}
输出指定 key 的值。%X{requestId}
-> rId-12345
%throwable
java.io.FileNotFoundException: ...
%highlight
%highlight{%p}
会让 ERROR 显示为红色%r
15234
(毫秒)%F
Main.java
常用格式示例
- 标准开发/调试格式:这种格式信息非常全,便于调试,但因为包含了方法名(
%M
)和行号(%L
),性能有损耗,不建议在生产环境使用。
%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36}.%M(%L) - %msg%n
说明:
%-5level
:-5
表示左对齐并固定宽度为5个字符,这样各级别(INFO, DEBUG, ERROR)就能对齐,更美观。
- 生产环境格式:生产环境通常更关心上下文(时间、级别、线程、Logger名)和业务消息,避免使用高开销的
%M
和%L
。
%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
- 高亮模式:在 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 参数来明确指定要使用哪个环境的配置。
-
创建不同环境的配置文件
在你的项目资源目录(如src/main/resources
)下创建多个配置文件:log4j2-dev.xml
- 开发环境配置(级别为DEBUG
, 输出到控制台)[代码略]
log4j2-pro.xml
- 生产环境配置(级别为INFO
, 输出到文件和时间滚动归档)[代码略]
-
在启动命令中指定环境
通过 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 的架构主要由以下几个核心部分组成:
-
LoggerContext:日志系统的入口和上下文环境。
-
Configuration:配置信息的容器,包括 Appender、LoggerConfig、Filter 等。
-
Logger:用户直接使用的日志记录接口。
-
LoggerConfig:配置每个 Logger 的行为(如级别、Appender、Filter)。
-
Appender:定义日志输出的目的地(如文件、控制台、网络)。
-
Layout:定义日志输出的格式。
-
Filter:在不同级别上过滤日志事件。
-
StrSubstitutor / Interpolator / StrLookup:处理配置中的变量替换(如
${env:USER}
)。
4.1.2 运作机理概述
-
用户代码调用 Logger:
logger.info(\"This is a log message\");
-
Logger 委托给 LoggerConfig:
-
Logger 本身不处理日志,而是委托给其对应的 LoggerConfig。
-
LoggerConfig 根据配置决定是否处理该日志(基于 Level 和 Filter)。
-
-
LoggerConfig 调用 AppenderControl:
-
每个 LoggerConfig 包含一个或多个 AppenderControl。
-
AppenderControl 是对 Appender 的封装,可能附加 Filter。
-
-
Appender 输出日志:
-
Appender 使用 Layout 格式化日志事件。
-
最终输出到目的地(文件、控制台、数据库等)。
-
-
Filter 机制:
-
Filter 可以在四个级别上设置:Configuration、LoggerConfig、AppenderControl、Appender。
-
每个 Filter 决定是否接受、拒绝或中立处理 LogEvent。
-
-
变量替换:
- 使用
StrSubstitutor
→Interpolator
→StrLookup
链解析${...}
表达式。
- 使用
4.1.3 Log4j2.xml理解
当用户调用 Logger 时,Log4j 会读取配置文件信息,根据配置文件来判断如何处理用户的日志打印。
而每个配置文件上的每个标签都有具体的含义,下图是简要的解读:
简单说,Log4j 通过配置驱动和责任链模式实现了高度可扩展的日志系统。用户只需与 Logger
交互,而底层的 LoggerConfig
、Appender
、Filter
、Layout
等组件通过配置灵活组合,满足各种日志需求。
4.2 多日志框架混用的统一策略
事实上,Java主流的日志框架,除了 log4j,其实还有 logback、Slf4j等。而大型项目是由很多组件构成,每个组件可能最终使用的日志框架不尽一样。而 Log4j 的开发者其实也考虑到了这一点,利用桥接模式巧妙的解决了多日志框架混用的日志输出混乱问题。下图是 Log4j 官网的多日志框架桥接适配方式图。
项目本身或存在多个模块或组件(Application、Library 1/2/3),使用了不同的日志框架。默认用户调用的都是各个日志框架的API层(SLF4J、JUL、JPL、Log4j API),通过桥接器的方式(SLF4J-to-Log4j
、JUL-to-Log4j
、JPL-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
桥接器。 -
注意:避免同时引入多个桥接器或日志实现,防止冲突。
好啦,以上就是今天的讲解内容啦,感谢阅读。