Log4j源代码学习

  了解log4j的源代码来源于项目中一次需求,我们想将系统所有的warn日志统一收集到common-warn.log的日志中去,以便于系统对其进行监控处理。于是模拟自动生成的error配置完成了warn的配置,但是测试发现common-warn.log中竟然有error日志,而且业务的正常日志中竟然也存在error和warn日志。这样相当于日志重复打了好多地方,无疑增加了日志量,同时增加了磁盘消耗。

原始配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<appender name="DEFAULT-APPENDER" class="org.apache.log4j.DailyRollingFileAppender">
       <param name="file" value="${log_root}/${sys_host_name}/app-service.log"/>
       <param name="append" value="true"/>
       <param name="encoding" value="UTF-8"/>
       <layout class="org.apache.log4j.PatternLayout">
           <param name="ConversionPattern"
                  value="%d [%X{loginUserEmail}/%X{loginUserID}/%X{remoteAddr}/%X{clientId} - %X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
       </layout>
   </appender>
 
   <!-- [公共Appender] 汇总错误 -->
   <appender name="ERROR-APPENDER" class="org.apache.log4j.DailyRollingFileAppender">
       <param name="file" value="${log_root}/${sys_host_name}/common-error.log"/>
       <param name="append" value="true"/>
       <param name="encoding" value="UTF-8"/>
       <param name="threshold" value="error"/>`
       <layout class="org.apache.log4j.PatternLayout">
           <param name="ConversionPattern"
                  value="%d [%X{loginUserEmail}/%X{loginUserID}/%X{remoteAddr}/%X{clientId} - %X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
       </layout>
   </appender>
 
   <!-- [公共Appender] 汇总警告 -->
   <appender name="WARN-APPENDER" class="org.apache.log4j.DailyRollingFileAppender">
       <param name="file" value="${log_root}/${sys_host_name}/common-warn.log"/>
       <param name="append" value="true"/>
       <param name="encoding" value="UTF-8"/>
       <param name="threshold" value="WARN"/>
       <layout class="org.apache.log4j.PatternLayout">
           <param name="ConversionPattern"
                  value="%d [%X{loginUserEmail}/%X{loginUserID}/%X{remoteAddr}/%X{clientId} - %X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
       </layout>
   </appender>
 
   <!-- [应用Logger]  - 默认 -->
   <logger name="APP-LOG" additivity="false">
       <level value="${log_level}"/>
       <appender-ref ref="DEFAULT-APPENDER"/>
       <appender-ref ref="WARN-APPENDER"/>
       <appender-ref ref="ERROR-APPENDER"/>
   </logger>

  


所以我的核心诉求就是:将小于等于info的日志打印到app-service.log,将error打印到common-error.log, 将warn打印到common-warn.log。

1. Log4j核心类

     核心抽象:

  • Logger 用于对日志记录行为的抽象,提供记录不同级别日志的统一接口;
  • Level对日志级别的抽象;
  • Appender是对记录日志形式的抽象,标示了日志打印的目的地;
  • Layout是对日志行格式的抽象;
  • LoggingEvent是对一次日志记录过程中所需要的信息的抽象,可以理解成一个上下文;

  整个日志打印的过程可以理解为Loger拿着LoggingEvent去找Appender, 让Appender按照Layout的形式将日志打印到指定的位置。 而Level起的啥作用呢? Logger和Appender都是有原则的不能说你让我打印我就打印,必须满足我的规则我才给你打印,这里的规则就是指Level(出来混都是要讲原则的)。

核对类图:

 

 


2. 初始化过程

  核心逻辑在LogManager的静态代码块,根据配置信息解析初始化Logger,Appender和Layout等核心组件;

     

 

 

3. 日志打印  

  核心的日志打印流程, 中间还有很多控制的细节,具体可以看源代码:

     

 

4. 日志控制

  回到我们的出发点,如何实现:
  将小于等于info的日志打印到app-service.log,将error打印到common-error.log, 将warn打印到common-warn.log。

  • 1. 重写Appender中2.5.2的方法(isAsSevereAsThreshold),自定义三个Appender的类,Appender1判断满足小于等于info的LoggingEvent才进行打印,目标指定为app-service.log;Appender2判断满足等于warn的LoggingEvent才进行打印,目标指定为common-warn.log;Appender3判断满足等于error的LoggingEvent才进行打印,目标指定为common-error.log;这样能够满足我们的需求,但是这样就会存两个问题
    •  存在一个潜规则就是:开发在配置的时候必须要使用自定义的Appender才可以满足,如果某同学使用默认的话,就可能存在日志打印错乱,影响监控;
    •  如果某个开发在Logger配置的时候忘记指定error的Appender,那么error日志将不会打印,存在日志丢失,风险很大;
  • 2. 添加2.5.3中提到的Filter配置,进行Appender过滤。log4j支持DenyAllFilter,LevelMatchFilter,LevelRangeFilter,StringMatchFilter的配置。通过LevelRangeFilter可以指定该Appender支持的日志范围。该方案解决了上个方案a存在的问题,但是无法解决b可能存在的问题;
  • 3. 重写Logger的日志打印的方法,在遍历Appender执行的时候,首先筛选出Level匹配(这里只相等)的Appender进行打印,如果没有匹配的Appender,再按照默认的策略进行打印。这样可以通过设置Appender的threshold即可实现,而且还可以防止Appender漏配置导致的日志缺失问题。该方案既可以兼容日志丢失,又可以满足我们的需求,但是logger对象中的AppenderAttachableImpl存储Appdender的List ,Logger中直接存放的是实现类,没有提供可扩展的方式,好像不太好实现。 求大神指点。 

      目前项目中使用的方案为2, 同时app-service.log的Appender不配置Filter默认接受error和warn的日志, 但是warn和error通过Fileter进行区分不会相互影响,这样可以满足业务监控warn和error日志的需求,不需要修改任何代码,同时满足不会丢失日志的问题。但是存在error和warn日志重复打印的问题。 

 

最终的解决方案配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<appender name="DEFAULT-APPENDER" class="org.apache.log4j.DailyRollingFileAppender">
    <param name="file" value="${log_root}/${sys_host_name}/app-default.log"/>
    <param name="append" value="true"/>
    <param name="encoding" value="UTF-8"/>
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern"
               value="%d [%X{loginUserEmail}/%X{loginUserID}/%X{remoteAddr}/%X{clientId} - %X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
    </layout>
</appender>
 
<!-- [公共Appender] 汇总错误 -->
<appender name="ERROR-APPENDER" class="org.apache.log4j.DailyRollingFileAppender">
    <param name="file" value="${log_root}/${sys_host_name}/common-error.log"/>
    <param name="append" value="true"/>
    <param name="encoding" value="UTF-8"/>
    <param name="threshold" value="error"/>`
    <!-- 仅打印error级别的日志 -->
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
        <param name="levelMin" value="ERROR" />
        <param name="levelMax" value="ERROR"/>
        <param name="acceptOnMatch" value="true"/>
    </filter>
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern"
               value="%d [%X{loginUserEmail}/%X{loginUserID}/%X{remoteAddr}/%X{clientId} - %X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
    </layout>
</appender>
 
<!-- [公共Appender] 汇总警告 -->
<appender name="WARN-APPENDER" class="org.apache.log4j.DailyRollingFileAppender">
    <param name="file" value="${log_root}/${sys_host_name}/common-warn.log"/>
    <param name="append" value="true"/>
    <param name="encoding" value="UTF-8"/>
    <param name="threshold" value="WARN"/>
    <!-- 仅打印warn级别的日志 -->
    <filter class="org.apache.log4j.varia.LevelRangeFilter">
        <param name="levelMin" value="WARN" />
        <param name="levelMax" value="WARN"/>
        <param name="acceptOnMatch" value="true"/>
    </filter>
    <layout class="org.apache.log4j.PatternLayout">
        <param name="ConversionPattern"
               value="%d [%X{loginUserEmail}/%X{loginUserID}/%X{remoteAddr}/%X{clientId} - %X{requestURIWithQueryString}] %-5p %c{2} - %m%n"/>
    </layout>
</appender>
 
<!-- [应用Logger]  - 默认 -->
<logger name="APP-LOG" additivity="false">
    <level value="${log_level}"/>
    <appender-ref ref="DEFAULT-APPENDER"/>
    <appender-ref ref="WARN-APPENDER"/>
    <appender-ref ref="ERROR-APPENDER"/>
</logger>

  

posted @   E_star  阅读(4010)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
历史上的今天:
2013-11-20 Java中的RTTI
2012-11-20 SDUT 1008 最大公共子串 变维dp
点击右上角即可分享
微信分享提示