【日志打印】Log4j
阿里技术针对Java日志的专题系列: https://new.qq.com/rain/a/20240522A010GS00
Log4j2中RollingFile的文件滚动更新机制:https://www.cnblogs.com/yeyang/p/7944899.html
https://www.cnblogs.com/yeyang/p/7944899.html
https://open.alipay.com/portal/forum/post/137701054?ant_source=opendoc_recommend
日志打印实战(含同步、异步、内部实现原理图):https://www.cnblogs.com/yeyang/p/7944906.html
异步日志:性能优化的金钥匙 https://new.qq.com/rain/a/20240704A0122F00
Log4j2的结构:
- Logger: 这是开发者直接使用的接口,用于记录不同级别的日志信息(如DEBUG, INFO, ERROR等)。每个Logger都有一个名称,并且支持继承性,形成一个名为Logger Hierarchy的树状结构,根Logger的名称为"root"。
- LoggerContext: 是日志系统的上下文环境,管理着一组Logger实例以及它们的配置。每个应用程序通常只有一个LoggerContext,但它支持多个上下文以实现更细粒度的控制。
- Configuration: 每个LoggerContext都关联一个有效的Configuration,定义了日志的输出目的地(Appenders)、日志的过滤规则(Filters)、日志的格式化方式(Layouts)等。Configuration可以通过配置文件(如XML、JSON、properties)或编程方式动态加载。
- Appender: 负责将日志事件发送到指定的目标,如控制台(Console)、文件(File)、数据库、网络Socket等。
- Layout: 定义了日志信息的格式化方式,如模式字符串(Pattern String)决定了日期、时间、日志级别、线程名、日志信息等内容的排列和格式。
- Filter: 可以在日志事件从Logger传递到Appender的过程中进行过滤,根据特定条件决定日志是否被输出。
- Lookup: 提供动态值解析机制,如${ctx:variable}可以在日志中插入上下文变量的值。
log4j2.xml配置文件组成:
<Configuration> <Properties> </Properties> <appenders> </appenders> <loggers> </loggers> <Configuration>
一、<appenders>
RollingFileAppender日志绕接
RollingFileAppender是Log4j2中的一种能够实现日志文件滚动更新(rollover)的Appender。
rollover的意思是当满足一定条件(如文件达到了指定的大小,达到了指定的时间)后,就重命名原日志文件进行归档,并生成新的日志文件用于log写入。如果还设置了一定时间内允许归档的日志文件的最大数量,将对过旧的日志文件进行删除操作。
- TriggeringPolicy为触发策略,其决定了何时触发日志文件的rollover,即When。
- RolloverStrategy为滚动更新策略,其决定了当触发了日志文件的rollover时,如何进行文件的rollover,即How。
1.1 TriggeringPolicy 触发策略
定义:<Policies></Policies>
RollingFile的触发rollover的策略有:
- CronTriggeringPolicy(Cron表达式触发)
- OnStartupTriggeringPolicy(JVM启动时触发)
- SizeBasedTriggeringPolicy(基于文件大小):size参数可以用KB、MB、GB等做后缀来指定具体的字节数,如20MB。
-
TimeBasedTriggeringPolicy规定了当日志文件名中的date/time pattern不再符合filePattern中的date/time pattern时,触发rollover操作。
比如,filePattern指定文件重命名规则为app-%d{yyyy-MM-dd HH}.log,文件名为app-2017-08-25 11.log,当时间达到2017年8月25日中午12点(2017-08-25 12),将触发rollover操作。
参数1:interval
integer型,指定两次封存动作之间的时间间隔。这个配置需要和filePattern结合使用,filePattern日期格式精确到哪一位,interval也精确到哪一个单位。注意filePattern中配置的文件重命名规则是%d{yyyy-MM-dd HH-mm-ss}-%i,最小的时间粒度是ss,即秒钟。
TimeBasedTriggeringPolicy默认的size是1,结合起来就是每1秒钟生成一个新文件。如果改成%d{yyyy-MM-dd HH},最小粒度为小时,则每一个小时生成一个文件
参数2:modulate
boolean型,说明是否对封存时间进行调制。若modulate=true, 则封存时间将以0点为边界进行偏移计算。比如,modulate=true,interval=4hours, 那么假设上次封存日志的时间为03:00,则下次封存日志的时间为04:00, 之后的封存时间依次为08:00,12:00,16:00
- CompositeTriggeringPolicy(多个触发策略的混合,如同时基于文件大小和时间):将多个TriggeringPolicy放到Policies中表示使用复合策略,有一个条件满足即可
1.2 DefaultRolloverStrategy滚动更新策略
DefaultRolloverStrategy是Log4j2提供的默认的rollover策略,即使在log4j2.xml中没有显式指明,也相当于为RollingFile配置下添加了如下语句。DefaultRolloverStrategy默认的max为7
1.2.1 DeleteAction
DefaultRolloverStrategy制定了默认的rollover策略,通过max参数可控制一定时间范围内归档的日志文件的最大个数。
Log4j 2.5 引入了DeleteAction,使用户可以自己控制删除哪些文件,而不仅仅是通过DefaultRolloverStrategy的默认策略。
二、<Loggers>
常见2种:Root 和具体Logger
2.1 Root:
指定项目的根日志,如果没有单独指定logger,则默认使用Root输出;每个日志比如配置一个根Root,没有name属性,不需配置additivity属性
2.2 Logger
单独指定日志的形式,属性:
name: 指定该Logger所适用的类或所在包路径,继承自Root节点
level: 日志输出级别,未指定默认ERROR
AppenderRef: 指定该日志输出到哪个Appender,如果没指定,默认继承自Root。如果指定则会在指定Appender和Root的Appender都输出。
可以配置1个或多个AppenderRef属性。可以设置 additivity = "false"(默认true) 只在自定义的Appender进行输出
三、其他
1、MDC工具类
MDC: Mapped Diagnostic Context,可以粗略的理解成是一个线程安全的存放诊断日志的容器。内部通过ThreadLocal实现了不同线程的隔离。
参考:https://www.jianshu.com/p/8b01d62e2431 https://juejin.cn/post/6844904132478763022
内部实现:依赖MDCAdapter接口,不同的日志组件都实现如Log4jMDCAdapter
应用场景:
a)WEB应用,如果想在日志中输出请求用户IP 地址、请求 URL、统计耗时等等;
b)在WEB应用中,借助MDC来保存用户请求时产生的reqId,当请求完成后,再将reqId进行移除,这么通过grep reqId就能轻松get整个请求流程的日志轨迹;
c)在微服务盛行的当下,链路跟踪是个难题,而借助MDC去埋点
Seata代码
MDC API:
代码样例:需代码和log*.xml配合使用
效果:
多线程场景验证:
ThresholdFilter配置:onMatch表示匹配设定的日志级别后是DENY还是ACCEPT,onMismatch表示不匹配设定的日志级别是DENY还是ACCEPT还是NEUTRAL
match/misMatch指的是高于或等于设定的日志级别。所以,要先定义日志级别高的Filter。
onMatch和onMismatch都有三个属性值,分别为Accept、DENY和NEUTRAL
介绍一下这两个配置项的三个属性值:
- onMatch="ACCEPT" 表示匹配该级别及以上
- onMatch="DENY" 表示不匹配该级别及以上
- onMatch="NEUTRAL" 表示该级别及以上的,由下一个filter处理,如果当前是最后一个,则表示匹配该级别及以上
- onMismatch="ACCEPT" 表示匹配该级别以下
- onMismatch="NEUTRAL" 表示该级别及以下的,由下一个filter处理,如果当前是最后一个,则不匹配该级别以下的
- onMismatch="DENY" 表示不匹配该级别以下的
<ThresholdFilter level="FATAL" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
只打印INFO和ERROR
异步日志
使用log4j2的同步日志进行日志输出,日志输出语句与程序的业务逻辑语句将在同一个线程运行,
使用异步日志进行输出时,日志输出语句与业务逻辑语句并不是在同一个线程中运行,而是有专门的线程用于进行日志输出操作,处理业务逻辑的主线程不用等待即可执行后续业务逻辑。
Log4j2中的异步日志实现方式有AsyncAppender和AsyncLogger两种。
- AsyncAppender采用了ArrayBlockingQueue来保存需要异步输出的日志事件;
- AsyncLogger则使用了Disruptor框架来实现高吞吐。
AsyncAppender
每个Async Appender,内部维护了一个ArrayBlockingQueue,并将创建一个线程用于输出日志事件,如果配置了多个AppenderRef,将分别使用对应的Appender进行日志输出。
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="warn"> <Appenders> <RollingFile name="MyFile" fileName="logs/app.log"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> <SizeBasedTriggeringPolicy size="500MB"/> </RollingFile> <Async name="Async"> <AppenderRef ref="MyFile"/> </Async> </Appenders> <Loggers> <Root level="error"> <AppenderRef ref="Async"/> </Root> </Loggers> </Configuration>
AsyncLogger
内部使用了Disruptor框架。基于Disruptor开发的系统单线程能支撑每秒600万订单。
Disruptor框架内部核心数据结构为RingBuffer,其为无锁环形队列。
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="debug" name="MyApp" packages=""> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" /> </Console> <RollingFile name="RollingFile" fileName="logs/app.log" filePattern="logs/app-%d{yyyy-MM-dd HH}.log"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size="500MB"/> </Policies> </RollingFile> <RollingFile name="RollingFile2" fileName="logs/app2.log" filePattern="logs/app2-%d{yyyy-MM-dd HH}.log"> <PatternLayout> <Pattern>%d %p %c{1.} [%t] %m%n</Pattern> </PatternLayout> <Policies> <SizeBasedTriggeringPolicy size="500MB"/> </Policies> </RollingFile> </Appenders> <Loggers> <AsyncLogger name="com.meituan.Main" level="trace" additivity="false"> <appender-ref ref="RollingFile"/> </AsyncLogger> <AsyncLogger name="RollingFile2" level="trace" additivity="false"> <appender-ref ref="RollingFile2"/> </AsyncLogger> <Root level="debug"> <AppenderRef ref="Console"/> <AppenderRef ref="RollingFile"/> </Root> </Loggers> </Configuration>
测试代码:
public class Main {
public static void main(String args[]) {
Logger logger = LogManager.getLogger(Main.class);
Logger logger2 = LogManager.getLogger("RollingFile2");
Person person = new Person("Li", "lei");
logger.info("hello, {}", person);
logger2.info("good bye, {}", person);
}
在加载log4j2.xml的启动阶段,如果检测到配置了AsyncRoot或AsyncLogger,将启动一个disruptor实例。
生产者生产消息:当运行到类似于logger.info、logger.debug的输出语句时,将生成的LogEvent放入RingBuffer中。
消费者消费消息:如果RingBuffer中有LogEvent需要处理,EventProcessor线程从RingBuffer中取出LogEvent,调用Logger相关联的Appender输出LogEvent(具体输出过程与同步过程相同,同样需要过滤器过滤、PatternLayout格式化等步骤)。
如果RingBuffer中没有LogEvent需要处理,EventProcessor线程将处于等待阻塞状态(默认策略)。
需要注意的是,虽然在log4j2.xml中配置了多个AsyncLogger,但是并不是每个AsyncLogger对应着一个处理线程,而是仅仅有一个EventProcessor线程进行日志的异步处理。
同步异步打印方式对比
|
日志输出方式 |
sync |
同步打印日志,日志输出与业务逻辑在同一线程内,当日志输出完毕,才能进行后续业务逻辑操作 |
Async Appender |
异步打印日志,内部采用ArrayBlockingQueue,对每个AsyncAppender创建一个线程用于处理日志输出。 |
Async Logger |
异步打印日志,采用了高性能并发框架Disruptor,创建一个线程用于处理日志输出。 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix