SpringJava必知必会Java技术升华

教你用Log4j2和SLF4j打造完整的日志系统

2018-12-20  本文已影响416人  洋仔聊编程

一:前言

近期做一个项目打造项目的日志系统时,发现没有一个系统的学习,故准备系统学习一下日志系统,后续会有关于日志系统的其他介绍与总结,比如log4j2为什么这么快,其底层实现原理等。

java 界里有许多实现日志功能的工具,最早得到广泛使用的是 log4j,许多应用程序的日志部分都交给了 log4j,不过作为组件开发者,他们希望自己的组件不要紧紧依赖某一个工具,毕竟在同一个时候还有很多其他很多日志工具,假如一个应用程序用到了两个组件,恰好两个组件使用不同的日志工具,那么应用程序就会有两份日志输出了。

为了解决这个问题,JCL和SLF4j就出现了,JCL只提供 log 接口,具体的实现则在运行时动态寻找。这样一来组件开发者只需要针对JCL或者slf4j的接口开发,而调用组件的应用程序则可以在运行时搭配自己喜好的日志实践工具。跟 JCL 一样,SLF4J 也是只提供 log 接口,具体的实现是在打包应用程序时所放入的绑定器(名字为 slf4j-XXX-version.jar)来决定,XXX 可以是 log4j12, jdk14, jcl, nop 等,他们实现了跟具体日志工具(比如 log4j)的绑定及代理工作。举个例子:如果一个程序希望用 log4j 日志工具,那么程序只需针对 slf4j-api 接口编程,然后在打包时再放入 slf4j-log4j12-version.jar 和 log4j.jar 就可以了。

项目中我们选择了SLF4j+Log4j2来打造日志系统,log4j2的性能还是比Logback好一些的,下面有对比。

如果转载此博文,请附上本文链接:https://www.jianshu.com/p/56da0b0cad22 谢谢合作~

二:添加依赖

2.1:去除直接和间接依赖的log4j1和SLF4j

首先我们应该先删除项目已经依赖的其他日志组件,这里指的是没有用到的日志组件,例如janusgraph会间接依赖log4j1的组件,这个组件删除就会报错,所以我们只要删除没有使用的日志组件,这样可以使项目更加干净~
方法:我们可以观察项目目录下的External Libraries下的依赖文件,如果有log4j1或者其他日志依赖,我们将他们在pom文件中找到删除即可。


项目目录.jpg

如果依赖中有但是pom文件中找不到,就是被间接依赖进来的了,我们在pom 文件中右击鼠标,选中Diagrams->show dependences就可以看到整个项目的依赖图,在其中找到对应的log依赖,选中右击Exclude即可。


pom架构

2.2:添加依赖

添加的所有依赖都是截止2018.11.22日最新的稳定版本

<!--slf4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.25</version>
    <scope>runtime</scope>
</dependency>
<!--log4j2-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.1</version>
</dependency> 
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-web</artifactId>
    <version>2.11.1</version>
    <scope>runtime</scope>
</dependency>
<!--log4j2+slf4j-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.11.1</version>
</dependency>

上述的jcl-over-scf4j的作用以及原因: 即使现在你仍会看到很多程序应用 JCL + log4j 这种搭配,不过当程序规模越来越庞大时,JCL的动态绑定并不是总能成功,具体原因大家可以 Google 一下,这里就不再赘述了。解决方法之一就是在程序部署时静态绑定指定的日志工具,这也是 SLF4J 产生的原因。

现在还有一个问题,假如你正在开发应用程序所调用的组件当中已经使用了 JCL 的,还有一些组建可能直接调用了 java.util.logging,这时你需要一个桥接器(名字为 XXX-over-slf4j.jar)把他们的日志输出重定向到 SLF4J,所谓的桥接器就是一个假的日志实现工具,比如当你把 jcl-over-slf4j.jar 放到 CLASS_PATH 时,即使某个组件原本是通过 JCL 输出日志的,现在却会被 jcl-over-slf4j “骗到”SLF4J 里,然后 SLF4J 又会根据绑定器把日志交给具体的日志实现工具,这样就可以实现日志的统一了。如果你的项目没有使用jcl那么就不必添加这个。

上述的log4j-web是在开发web项目的时候需要的,如果你不是web项目,可以酌情删除

三:xml配置

3.1:log4j2.xml常用demo

在类路径下新建文件:log4j2.xml ,注意“2”不要缺少,位置放的正确并且文件名符合要求的话,项目会自动扫描到该配置文件。

Log4j2能够在初始化期间自动配置自身。当Log4j2启动时,它将找到所有ConfigurationFactory插件并按加权顺序从最高到最低排列。在交付时,Log4j包含四个ConfigurationFactory实现:一个用于JSON,一个用于YAML,一个用于 properties,一个用于XML,下面为查找加载顺序:

  1. Log4j2将检查“log4j.configurationFile”系统属性,如果设置,将尝试使用与文件扩展名匹配的ConfigurationFactory加载配置。
  2. 如果未设置系统属性,则ConfigurationFactory属性将在类路径中查找 log4j2-test.properties。
  3. 如果没有找到这样的文件,YAML ConfigurationFactory将在类路径中查找 log4j2-test.yaml或log4j2-test.yml。
  4. 如果没有找到这样的文件,JSON ConfigurationFactory将在类路径中查找 log4j2-test.json或log4j2-test.jsn。
  5. 如果找不到这样的文件,XML ConfigurationFactory将在类路径中查找 log4j2-test.xml。
  6. 如果找不到测试文件,ConfigurationFactory属性将在类路径上查找 log4j2.properties。
  7. 如果找不到属性文件,YAML ConfigurationFactory将在类路径上查找 log4j2.yaml或log4j2.yml。
  8. 如果找不到YAML文件,JSON ConfigurationFactory将在类路径上查找 log4j2.json或log4j2.jsn。
  9. 如果找不到JSON文件,XML ConfigurationFactory将尝试在类路径上找到 log4j2.xml。
  10. 如果找不到配置文件,则将使用DefaultConfiguration。这将导致所有日志记录输出转到控制台

log4j2.xml 文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO" monitorInterval="30">
    <properties>
        <!--设置容器日志在硬盘上输出的目录-->
        <property name="logPath">/opt/logs/renetwork/</property>
        <!--设置项目日志在硬盘上输出的目录-->
        <property name="logPathForProject">/opt/logs/renetwork/project/</property>
    </properties>

    <Appenders>
        <!--=====容器日志配置=====-->
        <!--设置在控制台打印日志-->
        <Console name="Console" target="SYSTEM_OUT">
            <!--设置输出格式-->
            <PatternLayout pattern="[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
        </Console>

        <!--设置级别为INFO日志输出到info.log中,filename为输出日志的目录,filepattern为压缩文件的命名规范与目录 -->
        <RollingFile name="INFO" filename="${logPath}/info.log"
                     filepattern="${logPath}/%d{YYYYMMdd}-%i-info.log.zip">
            <!--设置日志级别-->
            <Filters>
                <ThresholdFilter level="INFO"/>
            </Filters>
            <!--输出日志的格式-->
            <PatternLayout pattern="[ %-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <!--设置每天打包日志一次-->
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <!--设置日志文件满50MB后打包-->
                <SizeBasedTriggeringPolicy size="50MB" />
            </Policies>
            <!--设置最多保存20个日志文件,默认为7个-->
            <DefaultRolloverStrategy max="20" />
        </RollingFile>

        <!--设置级别为WARN日志输出到warn.log中-->
        <RollingFile name="WARN" filename="${logPath}/warn.log"
                     filepattern="${logPath}/%d{YYYYMMdd}-%i-warn.log.zip">
            <Filters>
                <!--设置只输出级别为WARN的日志-->
                <ThresholdFilter level="WARN"/>
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout pattern="[ %-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="50MB" />
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>

        <!--设置级别为ERROR日志输出到error.log中-->
        <RollingFile name="ERROR" filename="${logPath}/error.log"
                     filepattern="${logPath}/%d{YYYYMMdd}-%i-error.log.zip">
            <!--设置只输出级别为ERROR的日志-->
            <ThresholdFilter level="ERROR"/>
            <PatternLayout pattern="[ %-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="50MB" />
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>


        <!--=====项目日志配置=====-->
        <Console name="ConsolePro" target="SYSTEM_OUT">
            <PatternLayout pattern="[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
        </Console>

        <RollingFile name="INFOPro" filename="${logPathForProject}/info.log"
                     filepattern="${logPathForProject}/%d{YYYYMMdd}-%i-info.log.zip">
            <Filters>
                <ThresholdFilter level="INFO"/>
            </Filters>
            <PatternLayout pattern="[ %-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="50MB" />
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>

        <RollingFile name="WARNPro" filename="${logPathForProject}/warn.log"
                     filepattern="${logPathForProject}/%d{YYYYMMdd}-%i-warn.log.zip">
            <Filters>
                <ThresholdFilter level="WARN"/>
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout pattern="[ %-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="50MB" />
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>

        <RollingFile name="ERRORPro" filename="${logPathForProject}/error.log"
                     filepattern="${logPathForProject}/%d{YYYYMMdd}-%i-error.log.zip">
            <ThresholdFilter level="ERROR"/>
            <PatternLayout pattern="[ %-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n" />
            <Policies>
                <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
                <SizeBasedTriggeringPolicy size="50MB" />
            </Policies>
            <DefaultRolloverStrategy max="20" />
        </RollingFile>
    </Appenders>
    
    <Loggers>
        <!--
            添加项目日志
            配置项目日志只输出com.bj58.renetwork下面的日志
            additivity="false" 表示只将该日志输出到该配置的路径下面,并不会再重复输出到root也就是容器日志中
        -->
        <logger name="com.bj58.renetwork" level="debug" additivity="false">
            <appender-ref ref = "ConsolePro"/>
            <appender-ref ref = "INFOPro"/>
            <appender-ref ref = "WARNPro" />
            <appender-ref ref = "ERRORPro" />
        </logger>
        <!--添加容器日志-->
        <root level="INFO">
            <appender-ref ref = "Console"/>
            <appender-ref ref = "INFO" />
            <appender-ref ref = "WARN" />
            <appender-ref ref = "ERROR" />
        </root>
    </Loggers>
</Configuration>

注释我写的应该比较清楚了,如果你还是不太明白,下面我会详细介绍一下。

3.2:demo的优点

3.3:内容详解

1: 根节点Configuration有两个属性:status和monitorinterval

2:根节点下的子节点properties,用于定义变量和修改变量,这里我只定义了两个路径变量,一个是容器log路径,一个是项目log路径
3:根节点下的子节点Appenders,主要用于定义Appender,常见的有三种子节点:Console、RollingFile、File

log4j组件提供了好多种appender供我们使用,介绍看官网吧特别详细: http://logging.apache.org/log4j/2.x/manual/appenders.html

4:根节点下的子节点Loggers,用于配置上述添加的appender,两种子节点:Root、Logger

5:输出格式相关:

3.4:demo变形

3.4.1:同步打印日志

同步打印日志是最消耗资源的方式,我们在开发的时候,可以选择使用全同步方式打印日志,这样便于我们debug。或者项目并发度不高的情况下也可以使用这种方式。但是,当并发量比较大、对项目响应速度敏感时并且对日志不是强实时性要求的话,最好还是使用全部异步或者混合方式。

上述的demo便是全部同步的案例。在此不再赘述。

3.4.2:全部异步打印日志

全部异步打印日志是对项目请求速度最理想的方式,在500个线程的情况下速度几乎是全同步打印log的10倍,是混合打印的2倍。下面是官网的比较图,可以对照着看一下:


在这里插入图片描述

异步Logger是让业务逻辑把日志信息放入Disruptor队列后可以直接返回,具有更高吞吐、调用log方法更低的延迟。但也有一些缺点比如:异常处理麻烦、 可变日志消息问题、更大的CPU开销、需要等待“最慢的Appender”消费完成。

所以我们在并发量高、日志实时性要求不高,并且所暴漏的缺点都可以容忍的情况下最好还是选用全部异步打印日志,这样可以获得更快的响应,也会给用户更好的体验。
异步打印配置有几种方式:
1:在你的classpath下面添加个log4j2.component.properties文件,并且添加以下内容:

这种方式不需要修改原来的log4j2.xml文件

Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

2:使用异步标签,修改上面的demo的部分:

如果想要全部异步log的话,一定要所有的相关标签都是用异步标签

    <Loggers>
        <!--
            添加项目日志
            配置项目日志只输出com.bj58.renetwork下面的日志
            additivity="false" 表示只将该日志输出到该配置的路径下面,并不会再重复输出到root也就是容器日志中
        -->
        <asyncLogger name="com.bj58.renetwork" level="debug" additivity="false">
            <appender-ref ref = "ConsolePro"/>
            <appender-ref ref = "INFOPro"/>
            <appender-ref ref = "WARNPro" />
            <appender-ref ref = "ERRORPro" />
        </asyncLogger>
        <!--添加容器日志-->
        <asyncRoot level="INFO">
            <appender-ref ref = "Console"/>
            <appender-ref ref = "INFO" />
            <appender-ref ref = "WARN" />
            <appender-ref ref = "ERROR" />
        </asyncRoot>
    </Loggers>

3:JVM启动参数(boot.ini)加上:

-DLog4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector

3.4.3:混合模式打印日志

混合模式就是既有一异步又有同步日志打印,那些部分需要同步或者异步,这需要根据具体对项目该部分的需求来定了.

下面我设置了项目日志同步打印,容器日志异步打印

    <Loggers>
        <!--
            添加项目日志
            配置项目日志只输出com.bj58.renetwork下面的日志
            additivity="false" 表示只将该日志输出到该配置的路径下面,并不会再重复输出到root也就是容器日志中
        -->
        <logger name="com.bj58.renetwork" level="debug" additivity="false">
            <appender-ref ref = "ConsolePro"/>
            <appender-ref ref = "INFOPro"/>
            <appender-ref ref = "WARNPro" />
            <appender-ref ref = "ERRORPro" />
        </logger>
        <!--添加容器日志-->
        <asyncRoot level="INFO">
            <appender-ref ref = "Console"/>
            <appender-ref ref = "INFO" />
            <appender-ref ref = "WARN" />
            <appender-ref ref = "ERROR" />
        </asyncRoot>
    </Loggers>

四:其他

4.1:Log日志level

级别只输出“大于等于”自身级别的log,7中level的级别关系如下:
OFF > FATAL > ERROR > WARN > INFO > DEBUG > ALL
 1. DEBUG :
    DEBUG Level指出细粒度信息事件对调试应用程序是非常有帮助的。
 2. INFO
    INFO level表明 消息在粗粒度级别上突出强调应用程序的运行过程。
 3.WARN
    WARN level表明会出现潜在错误的情形。
 4.ERROR
    ERROR level指出虽然发生错误事件,但仍然不影响系统的继续运行。
 5.FATAL
    FATAL level指出每个严重的错误事件将会导致应用程序的退出。
 6.ALL
    ALL Level是最低等级的,用于打开所有日志记录。
 7.OFF
    OFF Level是最高等级的,用于关闭所有日志记录。

4.2:Log4j2与logback速度对比

Log4j2和logback都是日志组件,logback就是为了替代log4j1出现的,log4j2是log4j1的升级版,几乎相当于重构了log4j1。
log4j2的效率可以在多线程时,在线程数量大的情况下,超过logback10倍左右!下面是官网提供的数据对比:
速度对比图(来自官网):


image

在Solaris和windows操作系统上的数据对比(来自官网):


image

refer:
https://blog.csdn.net/zheng0518/article/details/69558893
https://blog.csdn.net/womeng2009/article/details/53510913/

您的喜欢与关注将是我前进最大的动力!!

上一篇 下一篇

猜你喜欢

热点阅读