Log4j2与Slf4j的最佳实践

Posted 程序员囧辉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Log4j2与Slf4j的最佳实践相关的知识,希望对你有一定的参考价值。

前言

日志对于项目的重要性不言而喻,现在市面上的日志框架多种多样:Log4j、Log4j2、Slf4j、JDKLog、Logback等等,如果没有真正深入了解过,可能会被搞得眼花缭乱。本文将介绍目前Java项目中最常见的Log4j2 + Slf4j的使用组合,这也是我自己项目中目前使用的。

另外,由于现在项目基本都是Servlet 3.0及以上版本,因此本文针对Servlet 3.0及更高的版本,如果使用的是Servlet 2.5可以参考官方文档进行适当的调整。


如何查看Servlet版本?

查看web.xml文件中<web-app>标签中的version字段即可。


关于Log4j2

在上面提到的日志框架中,以Log4j + Slf4j的使用组合最为常见,但是我们知道Log4j目前已经停止更新了。Apache推出了新的Log4j2来代替Log4j,Log4j2是对Log4j的升级,与其前身Log4j相比有了显着的改进,并提供了许多Logback可用的改进,同时解决了Logback体系结构中的一些固有问题。因此,Log4j2 + Slf4j应该是未来的大势所趋。


Log4j2的性能

Log4j2最牛逼的地方在于异步输出日志时的性能表现,Log4j2在多线程的环境下吞吐量与Log4j和Logback的比较如下图。下图比较中Log4j2有三种模式:1)全局使用异步模式;2)部分Logger采用异步模式;3)异步Appender。可以看出在前两种模式下,Log4j2的性能较之Log4j和Logback有很大的优势。

Log4j2与Slf4j的最佳实践

Log4j2完整的官方性能文档:http://logging.apache.org/log4j/2.x/performance.html


Log4j2使用的几个点

  1. 在Web项目中需要添加 log4j-web jar包。

  2. Log4j允许使用log4jConfiguration参数在web.xml中指定配置文件位置。Log4j将通过以下方式搜索配置文件:

    1. 如果配置了路径(log4jConfiguration参数配置),Log4j将去搜索这个位置。

    2. 如果未配置路径,Log4j将搜索WEB-INF目录中“log4j2”开头的文件。如果找到多个文件,并且存在以“log4j2-name”开头的文件,其中name是Web应用程序的名称,则会使用它。否则,将使用第一个文件。

    3. resources目录下搜索配置文件,规则同b。

  3. Log4j2不支持Servlet 2.4及更老的的Web应用程序。

第2点讲的简单点就是:Log4j2的配置文件名以“log4j2”开头时(例如常见的log4j2.xml),放在WEB-INF和resources的根路径时不需要在web.xml中配置路径,放在其他位置时需要配置路径。


基本的使用(同步模式)

1.maven依赖

<!--log4j2核心包-->
<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-api</artifactId>
   <version>2.9.1</version>
</dependency>
<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-core</artifactId>
   <version>2.9.1</version>
</dependency>
<!-- Web项目需添加 -->
<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-web</artifactId>
   <version>2.9.1</version>
</dependency>
<!--用于与slf4j保持桥接-->
<dependency>
   <groupId>org.apache.logging.log4j</groupId>
   <artifactId>log4j-slf4j-impl</artifactId>
   <version>2.9.1</version>
</dependency>
<!-- slf4j核心包-->
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-api</artifactId>
   <version>1.7.25</version>
</dependency>


2.在resources根路径下添加log4j2.xml配置文件(无需配置路径)

Log4j2与Slf4j的最佳实践

logj2.xml文件内容:将info及以上级别的日志输出到Console和指定路径的文件中。

Log4j2与Slf4j的最佳实践


3.web.xml配置log4j2配置文件的路径

如果log4j2.xml放在WEB-INF和resources根路径则不需要。

<!-- log4j2.xml路径 -->
<context-param>
   <param-name>log4jConfiguration</param-name>
   <param-value>/config/log4j2.xml</param-value>
</context-param>


4.如果是从Log4j等其他日志转的Log4j2 + Slf4j的,记得把Mavne中原来日志框架的依赖去掉或exclusion掉

常见的有:log4j和slf4j-log4j12,如下。

<dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.17</version>
</dependency>
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-log4j12</artifactId>
   <version>1.7.25</version>
</dependency>


5.代码中使用

import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @Author joonwhee
* @Date 2018/3/31
*/

@Controller
@RequestMapping("/user")
public class UserController {
   private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);
   @RequestMapping("/userList")
   public String userList(HttpServletRequest request, Model model) {
       LOGGER.info("Test log4j2 info");
       LOGGER.warn("Test log4j2 warn");
       LOGGER.error("Test log4j2 error");
       return "userList";
   }
}


6.使用结果

调用该方法后,Console输出:

Log4j2与Slf4j的最佳实践


在log4j2.xml文件中配置的路径下生成日志文件:

Log4j2与Slf4j的最佳实践


打开日志查看输出内容:

Log4j2与Slf4j的最佳实践

这里Console输出和日志文件输出“前缀”不完全一致是由于上面log4j2.xml中Console和RollingFile的PatternLayout配置不一致导致的。


至此,Log4j2集成Slf4j就完成了。


扩展测试

关于上面提到的Log4j2配置文件存放路径的问题,我们来做几个简单的测试:


场景1:log4j2.xml放在resources/config路径下,在web.xml中配置log4jConfiguration指定路径。

Log4j2与Slf4j的最佳实践

效果:日志正常输出。

结论:Log4j能找到配置文件。


场景2:log4j2.xml放在resources/config路径下,注释掉web.xml中的log4jConfiguration配置。

Log4j2与Slf4j的最佳实践

效果:明显看出我们的配置已经失效了,但是输出了ERROR级别的日志,可以推测这是Log4j在找不到配置文件时的兜底策略。

结论:Log4j不能找到配置文件。


场景3:log4j2.xml放在resources根路径下,注释掉web.xml中的log4jConfiguration配置。

Log4j2与Slf4j的最佳实践

效果:日志正常输出。

结论:Log4j能找到配置文件。


场景4:log4j2.xml放在WEB-INF根路径下,注释掉web.xml中的log4jConfiguration配置。

Log4j2与Slf4j的最佳实践

效果:日志正常输出。

结论:Log4j能找到配置文件。


看完测试结果,大家就可以根据自己的习惯将log4j2.xml放到自己喜欢的地方了。


进阶使用(异步模式)

异步模式下,默认情况不会输出位置信息,因为输出位置信息会慢30-100倍。如果需要位置信息,需要在所有相关记录器(包括根记录器)的配置中设置“includeLocation = true”。

例如以下代码:

<asyncRoot level="info" includeLocation="true">
   <AppenderRef ref="RandomAccessFile"/>
</asyncRoot>


什么是位置信息?

直接看下面两张图就很明显了。

有位置信息:

Log4j2与Slf4j的最佳实践

无位置信息:

Log4j2与Slf4j的最佳实践


1.全局使用异步(在同步模式的基础上修改)

1.maven增加disruptor依赖,Log4j2版本2.9及以上时需要disruptor-3.3.4.jar或更高版本;Log4j2版本2.9以下时需要disruptor-3.0.0.jar或更高版本。

<dependency>
   <groupId>com.lmax</groupId>
   <artifactId>disruptor</artifactId>
   <version>3.3.4</version>
</dependency>


2.将系统属性log4j2.contextSelector设置为org.apache.logging.log4j.core.async.AsyncLoggerContextSelector。

方式:添加一个名字为log4j2.component.properties的文件,放到classpath下面,log4j2会在启动的时候自动加载。如下:

Log4j2与Slf4j的最佳实践


3.log4j2.xml的配置修改成如下,跟同步模式的区别为RollingFile变成了RandomAccessFile。

Log4j2与Slf4j的最佳实践


2.部分Logger采用异步方式(在同步模式的基础上修改)

1.maven增加disruptor依赖,Log4j2版本2.9及以上时需要disruptor-3.3.4.jar或更高版本;Log4j2版本2.9以下时需要disruptor-3.0.0.jar或更高版本。

<dependency>
   <groupId>com.lmax</groupId>
   <artifactId>disruptor</artifactId>
   <version>3.3.4</version>
</dependency>


2.使用<asyncRoot>或<asyncLogger>配置来指定需要异步的记录器。

<asyncRoot level="info">
   <AppenderRef ref="RandomAccessFile"/>
</asyncRoot>


3.log4j2.xml的配置修改成如下,跟同步模式的区别为:1)RollingFile变成了RandomAccessFile;2)loggers中使用了<asyncRoot>或<asyncLogger>。

Log4j2与Slf4j的最佳实践


注意:配置只能包含一个根记录器(<root>或<asyncRoot>元素),但可以组合异步和非异步记录器。例如,包含<asyncLogger>元素的配置文件也可以包含同步记录器的<Root>和<Logger>元素。



简单性能测试

简单的测试下以下三种模式的耗时:全局使用异步的Log4j2、部分Logger使用异步的Log4j2、使用同步模式的Log4j2。


测试代码

1.测试例子很简单,就是将之前的3行输出外面加个64万次的循环。

Log4j2与Slf4j的最佳实践

2.将输出Console去掉,只输出到文件,同步模式下的Appenders为RollingFile;异步模式下的Appenders为RandomAccessFile。


场景1:RollingFile + 同步模式不带位置信息

场景1的配置文件主要内容如下

Log4j2与Slf4j的最佳实践

测试结果如下图:总共耗时17678ms

Log4j2与Slf4j的最佳实践

Log4j2与Slf4j的最佳实践


场景2:RollingFile + 同步模式带位置信息

场景2的配置文件主要内容如下,跟场景1只有includeLocation属性值不同

Log4j2与Slf4j的最佳实践

测试结果如下图:总共耗时257268ms

Log4j2与Slf4j的最佳实践

Log4j2与Slf4j的最佳实践


场景3:RandomAccessFile + 全局使用异步

场景3的配置文件主要内容如下

Log4j2与Slf4j的最佳实践

Log4j2与Slf4j的最佳实践


测试结果如下图:总共耗时2303ms

Log4j2与Slf4j的最佳实践

Log4j2与Slf4j的最佳实践


场景4:RandomAccessFile + 部分使用异步

场景4的配置文件主要内容如下

Log4j2与Slf4j的最佳实践

测试结果如下图:总共耗时5864ms

Log4j2与Slf4j的最佳实践

Log4j2与Slf4j的最佳实践


输出384万行简单日志的测试结果统计:


以上测试只是简单的测试,详细的性能比较可以查看官方文档:

http://logging.apache.org/log4j/2.x/performance.html


结论:

  1. 通过测试结果可以看出,Log4j2的异步模式性能提升还是比较明显的,但是需根据实际情况来确认是否需要。正常项目使用同步模式一般都够用了。

  2. 输出位置信息带来的性能损耗太高了。因此,在线上项目的日志记录一定不要输出位置信息。


—————END—————



以上是关于Log4j2与Slf4j的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

logback最佳实践

将 log4j2 与 slf4j 一起使用:java.lang.***Error

log4j2 核弹大锅,顺便学习SLF4J与log4j2

在android中使用底部导航的最佳实践:活动与片段

更新片段参数的最佳实践?

Slf4j与log4j及log4j2的关系及使用方法