【日志打印】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写入。如果还设置了一定时间内允许归档的日志文件的最大数量,将对过旧的日志文件进行删除操作。

 RollingFile实现日志文件滚动更新,依赖于TriggeringPolicy和RolloverStrategy。
  • 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,创建一个线程用于处理日志输出。

 

posted @   飞翔在天  阅读(146)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示