Log4j源代码学习

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

原始配置:

 <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日志重复打印的问题。

最终的解决方案配置:

    <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>

  

时间: 2024-08-01 22:53:37

Log4j源代码学习的相关文章

nginx源代码学习资源(不断更新)

nginx源代码学习是一个痛苦又快乐的过程,以下列出了一些nginx的学习资源. 首先要做的当然是下载一份nginx源代码,能够从nginx官方站点下载一份最新的. 看了nginx源代码,发现这是一份全然没有凝视,全然没有配置文档的代码. 如今你最希望要的是一份凝视版的nginx源代码,能够从以下的链接中下载一份: https://github.com/jianfengye/nginx-1.0.14_comment 这份凝视版源代码会不断进行更新的 好了,第一个问题, nginx的main函数在

tablib源代码学习

tablib简介 ----------- Tablib is a format-agnostic tabular dataset library, written in Python. Tablib 是一个格式未知的表格操作库,使用python编写,目前(2014-06-11)支持如下格式:Excel .JSON .YAML .HTML.TSV .CSV的导入/导出,及修改操作.实现方法是使用各种数据格式的python支持库(大多是各种格式的有明支持库)导入数据成list(列表,python 内

Log4j简单学习笔记

log4j结构图: 结构图展现出了log4j的主结构.logger:表示记录器,即数据来源:appender:输出源,即输出方式(如:控制台.文件...)layout:输出布局 Logger机滤器:常用级别的划分:Debug,Info,Warn,Error,Fatal这5个级别由低到高,如果配置的级别为"INFO"那么"Debug"级别的信息则不会显示"依次类推. 示例代码: @Test public void testLevel() { log.debu

Java进阶学习(2)——log4j的学习和使用

Java进阶学习(2)--log4j的学习和使用 简介Loj4j Log4j的组成 Log4j主要由三大组组件构成: Logger: 负责生成日志,并能够对日志信息进行分类筛选,通俗的讲就是决定什么日志信息应该被输出,什么日志信息应该被忽略. Appender: 定义了日志信息输出的目的地,指定日志信息应该被输出到什么地方,这些地方可以是控制台.文件或网络设备等. Layout: 指定日志信息的输出格式. 说明: 一个Logger可以有多个Appender,这意味着日志信息可以被输出到多个设备上

struts2源代码学习之初始化(一)

看struts2源代码已有一段时日,从今天開始,就做一个总结吧. 首先,先看看怎么调试struts2源代码吧,主要是下面步骤: 使用Myeclipse创建一个webproject 导入struts2须要的jar包 如图: 让jar包关联源文件 在上图中的jar包右键,选择properties->java source attach,假设关联成功,双击jar包下的某个class文件就会显示java源码了. 双击.class文件,在源码关键地方设置断点 部署project到Tomcat Tomcat

[Java] LinkedList / Queue - 源代码学习笔记

简单地画了下 LinkedList 的继承关系,如下图.只是画了关注的部分,并不是完整的关系图.本博文涉及的是 Queue, Deque, LinkedList 的源代码阅读笔记.关于 List 接口的笔记,可以参考上一篇博文 List / ArrayList - 源代码学习笔记 Queue 1. 继承 Collection 接口,并提供了额外的插入.提取和查看元素的方法.新增的方法都有两种形式:当操作失败时,抛出异常或者返回一个特殊值.特殊值可以是 null 或者 false ,这取决于方法本

JDK源代码学习系列04----ArrayList

                                                                         JDK源代码学习系列04----ArrayList 1.ArrayList简单介绍 ArrayList是基于Object[] 数组的,也就是我们常说的动态数组.它能非常方便的实现数组的添加删除等操作. public class ArrayList<E> extends AbstractList<E> implements List<

igmpproxy源代码学习——igmpProxyInit()

igmpproxy源代码学习--igmpProxyInit()函数详解,igmpproxy初始化 在运行igmpproxy的主程序igmpproxyRun()之前需要对igmpproxy进行一些配置,这些配置都是在igmpProxyInit()中完成的. 要进行的配置主要有: 信号处理配置 物理网络接口配置加载 配置文件的加载 虚拟网络设备初始化 路由向量表初始化 定时器初始化 信号处理配置 首先进行信号处理配置: sigemptyset(&sa.sa_mask); sigaction(SIGT

lucene源代码学习之LZ4压缩算法在lucene中应用

LZ4算法又称为Realtime Compression Algorithm,在操作系统(linux/freeBSD).文件系统(OpenZFS).大数据(Hadoop).搜索引擎(Lucene/solr).数据库(Hbase)--都可以看到它的身影,可以说是一个非常通用的算法.LZ4最突出的地方在于它的压缩/解压速度. 基础知识 理解Lucene中LZ4算法的实现,需要有以下两点基础知识: 1. 理解Lucene里面的packedInts. 关于PacedInts,可以参考http://sbp