Log4j 介绍过了,SLF4J 介绍过了,Logback 也介绍过了,你以为日志系列的文章就到此终结了?

不不不,我告诉你,还有一个 Log4j 2,顾名思义,它就是 Log4j 的升级版,就好像手机里面的 Pro 版。我作为一个写文章方面的工具人,或者叫打工人,怎么能不写完这最后一篇。

Log4j、SLF4J、Logback 是一个爹——Ceki Gulcu,但 Log4j 2 却是例外,它是 Apache 基金会的产品。

SLF4J 和 Logback 作为 Log4j 的替代品,在很多方面都做了必要的改进,那为什么还需要 Log4j 2 呢?我只能说 Apache 基金会的开发人员很闲,不,很拼,要不是他们这种精益求精的精神,这个编程的世界该有多枯燥,毕竟少了很多可以用“拿来就用”的轮子啊。

上一篇也说了,老板下死命令要我把日志系统切换到 Logback,我顺利交差了,老板很开心,夸我这个打工人很敬业。为了表达对老板的这份感谢,我决定偷偷摸摸地试水一下 Log4j 2,尽管它还不是个成品,可能会会项目带来一定的隐患。但谁让咱是一个敬岗爱业的打工人呢。


01、Log4j 2 强在哪

1)在多线程场景下,Log4j 2 的吞吐量比 Logback 高出了 10 倍,延迟降低了几个数量级。这话听起来像吹牛,反正是 Log4j 2 官方自己吹的。

Log4j 2 的异步 Logger 使用的是无锁数据结构,而 Logback 和 Log4j 的异步 Logger 使用的是 ArrayBlockingQueue。对于阻塞队列,多线程应用程序在尝试使日志事件入队时通常会遇到锁争用。

下图说明了多线程方案中无锁数据结构对吞吐量的影响。 Log4j 2 随着线程数量的扩展而更好地扩展:具有更多线程的应用程序可以记录更多的日志。其他日志记录库由于存在锁竞争的关系,在记录更多线程时,总吞吐量保持恒定或下降。这意味着使用其他日志记录库,每个单独的线程将能够减少日志记录。


性能方面是 Log4j 2 的最大亮点,至于其他方面的一些优势,比如说下面这些,可以忽略不计,文字有多短就代表它有多不重要。

2)Log4j 2 可以减少垃圾收集器的压力。

3)支持 Lambda 表达式。

4)支持自动重载配置。

02、Log4j 2 使用示例

废话不多说,直接实操开干。理论知识有用,但不如上手实操一把,这也是我多年养成的一个“不那么良好”的编程习惯:在实操中发现问题,解决问题,寻找理论基础。

第一步,在 pom.xml 文件中添加 Log4j 2 的依赖:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.5</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.5</version>
</dependency>

(这个 artifactId 还是 log4j,没有体现出来 2,而在 version 中体现,多少叫人误以为是 log4j)

第二步,来个最简单的测试用例:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Demo {
    private static final Logger logger = LogManager.getLogger(Demo.class);
    public static void main(String[] args) {
        logger.debug("log4j2");
    }
}

运行 Demo 类,可以在控制台看到以下信息:

ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.

Log4j 2 竟然没有在控制台打印“ log4j2”,还抱怨我们没有为它指定配置文件。在这一点上,我就觉得它没有 Logback 好,毕竟人家会输出。

这对于新手来说,很不友好,因为新手在遇到这种情况的时候,往往不知所措。日志里面虽然体现了 ERROR,但代码并没有编译出错或者运行出错,凭什么你不输出?

那作为编程老鸟来说,我得告诉你,这时候最好探究一下为什么。怎么做呢?

我们可以复制一下日志信息中的关键字,比如说:“No log4j2 configuration file found”,然后在 Intellij IDEA 中搜一下,如果你下载了源码和文档的话,不除意外,你会在 ConfigurationFactory 类中搜到这段话。

可以在方法中打个断点,然后 debug 一下,你就会看到下图中的内容。


通过源码,你可以看得到,Log4j 2 会去寻找 4 种类型的配置文件,后缀分别是 properties、yaml、json 和 xml。前缀是 log4j2-test 或者 log4j2。

得到这个提示后,就可以进行第三步了。

第三步,在 resource 目录下增加 log4j2-test.xml 文件(方便和 Logback 做对比),内容如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="DEBUG">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

Log4j 2 的配置文件格式和 Logback 有点相似,基本的结构为 < Configuration> 元素,包含 0 或多个 < Appenders> 元素,其后跟 0 或多个 < Loggers> 元素,里面再跟最多只能存在一个的 < Root> 元素。

1)配置 appender,也就是配置日志的输出目的地。

有 Console,典型的控制台配置信息上面你也看到了,我来简单解释一下里面 pattern 的格式:

  • %d{HH:mm:ss.SSS} 表示输出到毫秒的时间

  • %t 输出当前线程名称

  • %-5level 输出日志级别,-5 表示左对齐并且固定输出 5 个字符,如果不足在右边补空格

  • %logger 输出 logger 名称,最多 36 个字符

  • %msg 日志文本

  • %n 换行

顺带补充一下其他常用的占位符:

  • %F 输出所在的类文件名,如 Demo.java

  • %L 输出行号

  • %M 输出所在方法名

  • %l 输出语句所在的行数, 包括类名、方法名、文件名、行数

  • %p 输出日志级别

  • %c 输出包名,如果后面跟有 {length.} 参数,比如说 %c{1.},它将输出报名的第一个字符,如 com.itwanger 的实际报名将只输出 c.i

再次运行 Demo 类,就可以在控制台看到打印的日志信息了:

10:14:04.657 [main] DEBUG com.itwanger.Demo - log4j2

2)配置 Loggers,指定 Root 的日志级别,并且指定具体启用哪一个 Appenders。

3)自动重载配置

Logback 支持自动重载配置,Log4j 2 也支持,那想要启用这个功能也非常简单,只需要在 Configuration 元素上添加 monitorInterval 属性即可。

<Configuration monitorInterval="30">
...
</Configuration>

注意值要设置成非零,上例中的意思是至少 30 秒后检查配置文件中的更改。最小间隔为 5 秒。

03、Async 示例

除了 Console,还有 Async,可以配合文件的方式来异步写入,典型的配置信息如下所示:

<Configuration>
  <Appenders>
    <File name="DebugFile" fileName="debug.log">
      <PatternLayout>
        <Pattern>%d %p %c [%t] %m%n</Pattern>
      </PatternLayout>
    </File>
    <Async name="Async">
      <AppenderRef ref="DebugFile"/>
    </Async>
  </Appenders>
  <Loggers>
    <Root level="debug">
      <AppenderRef ref="Async"/>
    </Root>
  </Loggers>
</Configuration>

对比 Logback 的配置文件来看,Log4j 2 真的复杂了一些,不太好用,就这么直白地说吧!但自己约的,含着泪也得打完啊。把这个 Async 加入到 Appenders:

<Configuration>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <File name="DebugFile" fileName="debug.log">
            <PatternLayout>
                <Pattern>%d %p %c [%t] %m%n</Pattern>
            </PatternLayout>
        </File>
        <Async name="Async">
            <AppenderRef ref="DebugFile"/>
        </Async>
    </Appenders>
    <Loggers>
        <Root level="DEBUG">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="Async"/>
        </Root>
    </Loggers>
</Configuration>

再次运行 Demo 类,可以在项目根路径下看到一个 debug.log 文件,内容如下所示:

2020-10-30 09:35:49,705 DEBUG com.itwanger.Demo [main] log4j2

04、RollingFile 示例

当然了,Log4j 和 Logback 我们都配置了 RollingFile,Log4j 2 也少不了。RollingFile 会根据 Triggering(触发)策略和 Rollover(过渡)策略来进行日志文件滚动。如果没有配置 Rollover,则使用 DefaultRolloverStrategy 来作为 RollingFile 的默认配置。

触发策略包含有,基于 cron 表达式(源于希腊语,时间的意思,用来配置定期执行任务的时间格式)的 CronTriggeringPolicy;基于文件大小的 SizeBasedTriggeringPolicy;基于时间的 TimeBasedTriggeringPolicy。

过渡策略包含有,默认的过渡策略 DefaultRolloverStrategy,直接写入的 DirectWriteRolloverStrategy。一般情况下,采用默认的过渡策略即可,它已经足够强大。

来看第一个基于 SizeBasedTriggeringPolicy 和 TimeBasedTriggeringPolicy 策略,以及缺省 DefaultRolloverStrategy 策略的配置示例:

<Configuration>
  <Appenders>
    <RollingFile name="RollingFile" fileName="rolling.log"
                 filePattern="rolling-%d{yyyy-MM-dd}-%i.log">
      <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
      </PatternLayout>
      <Policies>
        <SizeBasedTriggeringPolicy size="1 KB"/>
      </Policies>
    </RollingFile>
  </Appenders>
  <Loggers>
    <Root level="debug">
      <AppenderRef ref="RollingFile"/>
    </Root>
  </Loggers>
</Configuration>

为了验证文件的滚动策略,我们调整一下 Demo 类,让它多打印点日志:

for (int i = 1;i < 20; i++) {
    logger.debug("微信搜索「{}」,回复关键字「{}」,有惊喜哦","沉默王二", "java");
}

再次运行 Demo 类,可以看到根目录下多了 3 个日志文件:


结合日志文件名,再来看 RollingFile 的配置,就很容易理解了。

1)fileName 用来指定文件名。

2)filePattern 用来指定文件名的模式,它取决于过渡策略。

由于配置文件中没有显式指定过渡策略,因此 RollingFile 会启用默认的 DefaultRolloverStrategy。

先来看一下 DefaultRolloverStrategy 的属性:


再来看 filePattern 的值 rolling-%d{yyyy-MM-dd}-%i.log,其中 %d{yyyy-MM-dd} 很好理解,就是年月日;其中 %i 是什么意思呢?

第一个日志文件名为 rolling.log(最近的日志放在这个里面),第二个文件名除去日期为 rolling-1.log,第二个文件名除去日期为 rolling-2.log,根据这些信息,你能猜到其中的规律吗?

其实和 DefaultRolloverStrategy 中的 max 属性有关,目前使用的默认值,也就是 7,那就当 rolling-8.log 要生成的时候,删除 rolling-1.log。可以调整 Demo 中的日志输出量来进行验证。

3)SizeBasedTriggeringPolicy,基于日志文件大小的时间策略,大小以字节为单位,后缀可以是 KB,MB 或 GB,例如 20 MB。

再来看一个日志文件压缩的示例,来看配置:

<RollingFile name="RollingFileGZ" fileName="gz/rolling.log"
             filePattern="gz/%d{yyyy-MM-dd-HH}-%i.rolling.gz">
    <PatternLayout>
        <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
    </PatternLayout>
    <Policies>
        <SizeBasedTriggeringPolicy size="1 KB"/>
    </Policies>
</RollingFile>
  • fileName 的属性值中包含了一个目录 gz,也就是说日志文件都将放在这个目录下。

  • filePattern 的属性值中增加了一个 gz 的后缀,这就表明日志文件要进行压缩了,还可以是 zip 格式。

运行 Demo 后,可以在 gz 目录下看到以下文件:


到此为止,Log4j 2 的基本使用示例就已经完成了。测试环境搞定,我去问一下老板,要不要在生产环境下使用 Log4j 2。


04、日志手册

到此为止,日志系统的全家桶 Log4j、SLF4J、Logback、Log4j 2 都被我搞定了。也就意味着,一份将近 2 万字的 PDF 诞生了!MD,我也要成为肝帝!

有了这份 PDF,硬气地告诉 Leader 或者老板吧,再也不用担心代码中会乱打印日志了。墙都不扶就服你。

地址我贴一下:

链接:https://pan.baidu.com/s/1dPwsQhT5OMVapE7hGi7vww 密码:fxxy

偷偷地告诉你吧,白嫖的感觉就是舒服,赶紧去下载吧!顺带点个赞,满足一下我的虚荣心,毕竟这是你们赐给我的最强的写作动力。


讲武德,你们要的高性能日志工具 Log4j2,来了的更多相关文章

  1. IntelliJ IDEA 2020.3正式发布,年度最后一个版本很讲武德

    仰不愧天,俯不愧人,内不愧心.关注公众号[BAT的乌托邦],有Spring技术栈.MyBatis.JVM.中间件等小而美的原创专栏供以免费学习.分享.成长,拒绝浅尝辄止.本文已被 https://ww ...

  2. 【腾讯Bugly干货分享】微信mars 的高性能日志模块 xlog

    本文来自于腾讯bugly开发者社区,未经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/581c2c46bef1702a2db3ae53 Dev Club 是一个交流移动 ...

  3. .NET日志工具介绍

    最近项目需要一个日志工具来跟踪程序便于调试和测试,为此研究了一下.NET日志工具,本文介绍了一些主流的日志框架并进行了对比.发表出来与大家分享. 综述 所谓日志(这里指程序日志)就是用于记录程序执行过 ...

  4. Android日志工具的使用

    一.使用Android的日志工具Log 1.Android中的日志工具类是Log,这个类中提供了如下5个方法来供我们打印日志. log.v():用于打印哪些最为繁琐.意义最小的日志信息.对应级别ver ...

  5. 日志工具全面理解及配置应用---以Log4j例子

    一.日志系统基本常识 1.日志系统作用:将日志信息输出到控制台和文本文件,以追踪代码运行信息. 2.日志系统操作的是什么?日志系统打印信息,也是调用日志系统的log.Info(),log.Warn() ...

  6. .NetCore中的日志(2)集成第三方日志工具

    .NetCore中的日志(2)集成第三方日志工具 0x00 在.NetCore的Logging组件中集成NLog 上一篇讨论了.NetCore中日志框架的结构,这一篇讨论一下.NetCore的Logg ...

  7. 细说Java主流日志工具库

    概述 在项目开发中,为了跟踪代码的运行情况,常常要使用日志来记录信息. 在Java世界,有很多的日志工具库来实现日志功能,避免了我们重复造轮子. 我们先来逐一了解一下主流日志工具. java.util ...

  8. Java主流日志工具库

    在项目开发中,为了跟踪代码的运行情况,常常要使用日志来记录信息.在Java世界,有很多的日志工具库来实现日志功能,避免了我们重复造轮子.我们先来逐一了解一下主流日志工具. 1.java.util.lo ...

  9. 【工具推荐】ELMAH——可插拔错误日志工具

    今天看到一篇文章(构建ASP.NET网站十大必备工具(2)),里面介绍了一个ELMAH的错误日志工具,于是研究了一下. ELMAH 是 Error Logging Modules and Handle ...

随机推荐

  1. Python常用模块之random和time

    常用模块: time: 分为三种格式: 1.时间戳:从1970年1月1日0点0分0秒到现在经过的秒数 用于时间间隔的计算 import time print(time.time()) 2.字符串显示时 ...

  2. Python之for循环和列表

    for循环: 有限循环 基本语法: for 变量 in 可迭代对象: 循环体 也可使用break,continue,for else list列表初识: 列表可放任意数据类型:[int,str,boo ...

  3. net core 微服务框架 Viper 调用链路追踪

    1.Viper是什么? Viper 是.NET平台下的Anno微服务框架的一个示例项目.入门简单.安全.稳定.高可用.全平台可监控.底层通讯可以随意切换thrift grpc. 自带服务发现.调用链追 ...

  4. JavaScript核心 Dom Bom

    <div id="time">2020-9-27</div> <script> //文档页面从上往下加载,先有标签才能获取元素对象,script ...

  5. C语言之 判断语句基础与if语句反汇编

    0x01.判断语句介绍以及用法 判断语句有哪些? 1.If 用法1: if (条件) { //代码块 } 当条件成立,也就是为True时,执行{}中的代码 用法2: if(条件) { //代码块 } ...

  6. 从原生web组件到框架组件源码(一)

    温馨提醒,当你觉得看我写的很乱的时候,就对了,那是因为我查阅了大量的资料提取出来的,因为有点东西不太理解,所以你会感觉有的部分重复了,也不是重复,只是后面对前面的内容进行梳理了一些,需要耐心的看到最后 ...

  7. 使用 Dockerfile 文件但是不使用缓存生成镜像

    前一段时候使用 Dockerfile 重新部署 NetCore3.1 项目的时候很顺利,由来由于一些原因,我把以前的镜像删除,如果我们大家继续使用 docker  build 命令去生成镜像的话就会报 ...

  8. scrapy反反爬虫

    反反爬虫相关机制 Some websites implement certain measures to prevent bots from crawling them, with varying d ...

  9. net core webapi多版本控制与swagger(nswag)配置

    前言 首先希望webapi 支持多版本,swagger针对不同的版本可进行交互.多版本控制基于Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer 包,swa ...

  10. CF1303G Sum of Prefix Sums

    点分治+李超树 因为题目要求的是树上所有路径,所以用点分治维护 因为在点分治的过程中相当于将树上经过当前$root$的一条路径分成了两段 那么先考虑如何计算两个数组合并后的答案 记数组$a$,$b$, ...