SLF4j + Log4j 、Log4j2、 Logback 日志框架使用【生产场景分析】

写在前面

从写代码开始,就陆陆续续接触到了许多日志框架,常用的Log4j 、Log4j2、 Logback等。每次自己写项目时,就copy别人的代码或网上的demo。配置log4j.properties或者logback.xml 就能搞定。但是一直没有理清各个框架之前的关系。然后总感觉打印日志的时候并不是随心所欲。特此简单分析分析。

在这里插入图片描述
SLF4j是日志门面,是一个日志标准,并非真正的日志实现,log4j、log4j2、logback才是真正的日志实现库。也就是说log4j、log4j2、logback 这兄弟三才是 真正打印日志的。

日志门面就是为了统一各个依赖的不同日志实现,便于我们自己项目中对类进行统一维护处理。

再来看看阿里java开发规范:

在这里插入图片描述

如题,这里选用常用的SLF4j + Log4j 、Log4j2、 Logback 进行分析。

在这里插入图片描述

在这里插入图片描述

slf4j是门面,大的设计模式是门面系统,而logback是直接实现了slf4j-api中的接口(已经默认实现了SLF4j的日志标准),是通过接口实现的方式完成了门面的实现。

而log4j和log4j2没有直接实现接口,所以需要个适配器。slf4j-log4j12和log4j-slf4j-impl就是适配器,将原本不能实现slf4j的变得也能实现这个标准了。添加了适配器后,就能使用slf4j的接口来使用log4j了。

各个库单独使用

  1. log4j
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

classpath下配置文件log4j.properties

log4j.rootLogger=INFO,console
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{
    
    yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n

使用:

import org.apache.log4j.Logger;
...
static final Logger LOGGER = Logger.getLogger(Main.class);
  1. log4j2
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.12.1</version>
</dependency>

classpath下log4j2.properties

rootLogger.level = info
rootLogger.appenderRef.stdout.ref = STDOUT

appender.console.type = Console
appender.console.name = STDOUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = %d{
    
    yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n

使用:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
...
static final Logger LOGGER = LogManager.getLogger(Main.class);
  1. logback
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

classpath下logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{
    
    yyyy-MM-dd HH:mm:ss} [%p] %c: %m%n</pattern>
        </encoder>
    </appender>
    <root level="debug">
        <appender-ref ref="console" />
    </root>
</configuration>

使用:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
static final Logger LOGGER = LoggerFactory.getLogger(Main.class);

注意:logback本身就是实现slf4j的,如上代码中的logger本就是slf4j的。然而log4j 和 log4j2 的logger都不是slf4j的。当然是可以升级的

为啥要升级?
是想要统一到一个门面标准

升级:基础库如何升级到SLF4j标准

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.29</version>
</dependency>

log4j2的实现方式,引入log4j-slf4j-impl ,也是加一个适配器

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.12.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.9.0</version>
</dependency>

这样组装后就可以用slf4j的写法了,统一了门面标准

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
...
static final Logger LOGGER = LoggerFactory.getLogger(Main.class);

实际项目中,往往会依赖于其它的包,这些依赖包可能就是不同的日志实现方式

生产场景一:某项目依赖到的其余三个包 的日志实现分别是 log4j log4j2 logback 。

在这里插入图片描述

这个时候我们不做统一处理的话,当然是不会冲突 报错的,只是打印的日志时候就会各自找各自的日志实现。

其中一种方式是可以 在我们的项目的redources下,新增 log4j.properties / log4j2.properties 和 logback.xml 共3个配置文件来自定义 (覆盖) 日志输出方式 ,这样,输出的日志也是会按照我们配置的进行正确输出。

生产场景二:某项目依赖到的其余三个包 的日志实现分别是 log4j log4j2 logback 。但是log4j 和 log4j2 已经统一了门面标准为SLF4J

在这里插入图片描述
这个实现我们项目在打印日志的时候,就会根据类加载随机选择一个日志实现,可能就是按照log4j的配置文件实现方式进行日志的打印

问题:这种情况如何指定特定的日志实现库呢?假如期望项目使用logback,并期望统一为slf化的logback形式,只配置一个logback.xml就能对所有依赖进行配置

这个时候可以分别
剔除A 依赖包的

         <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
            </exclusions>

和B 依赖包的

            <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-slf4j-impl</artifactId>
                </exclusion>
            </exclusions>

这个时候,只需要在我们项目的resources目录下配置一个logback.xml文件即可。

当然,很有可能我们在项目中是不清楚哪一个依赖包使用到了log4j 和 log4j2 的,这个时候我们是可以在 pom 文件的最后 写一个不存在的版本:

ps:maven加载方式按照最外层优先使用,如果引入一个不存在的,那么maven就不会加载这个依赖了。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>99.99.99</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>99.99.99</version>
</dependency>

也是能够达到去除slf化 ,从而使用统一的logback打印日志的效果的

生产场景三:某项目依赖到的其余三个包 的日志实现分别是 log4j log4j2 logback 。但是log4j 和 log4j2 并没有实现slf门面标准。

但是又想要统一为slf化的logback形式。怎么办呢?

在这里插入图片描述

也就是说要先对log4j 和 log4j2 进行slf 化才行。
思路:
1.先在整个项目下吧原有的log4j 和log4j2 剔除掉
2.再使用其它已经桥接好的log4j 和 log4j2 的包来代替slf 化的log4j 和 log4j2 的包

以下配置几乎是万能的,当遇到问题的时候,直接全部拷贝进去,稳定解决,绝不复发。

<!-- 处理单独log4j的依赖: -->
<!-- 用log4j-over-slf4j替换log4j,使依赖中的log4j也能"实现"slf4j-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.29</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>99.99.99</version>
</dependency>

<!-- 处理单独log4j2的依赖: -->
<!-- 用log4j-to-slf4j替换log4j2,使依赖中的log4j2也能"实现"slf4j -->
 <dependency>
    <groupId>org.apache.logging.log4j</groupId>
     <artifactId>log4j-to-slf4j</artifactId>
    <version>2.12.1</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>99.99.99</version>
</dependency>

<!-- 处理slf化的log4j的依赖: -->
<!-- 因为slf选binding的时候有多个候选,为防止slf4j-log4j12选中,直接去掉他 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>99.99.99</version>
</dependency>

<!-- 处理slf化的log4j2的依赖: -->
<!-- 因为slf选binding的时候有多个候选,为防止log4j-slf4j-impl选中,直接去掉他 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>99.99.99</version>
</dependency>

<!-- 最后引个新版本的logback -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

小结

  • slf4j-log4j12:与log4j联合使用,用于使当前项目的log4j实现slf标准
  • log4j-slf4j-impl:与log4j2联合使用,用于使当前项目的log4j实现slf标准
  • log4j-over-slf4j:与剔除log4j联合使用,替换log4j,使log4j实现slf。用于让单独用log4j的依赖能遵循slf,进而统一日志配置。
  • log4j-to-slf4j:与剔除log4j2联合使用,替换log4j2,使log4j2实现slf。用于让单独用log4j2的依赖能遵循slf,进而统一日志配置。

附阿里java开发规范:

1. 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架
SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
2. 【强制】日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
3. 【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:
appName_logType_logName.log。logType:日志类型,推荐分类有
stats/desc/monitor/visit 等;logName:日志描述。这种命名的好处:通过文件名就可知
道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
正例:mppserver 应用中单独监控时区转换异常,如:
mppserver_monitor_timeZoneConvert.log
说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于
通过日志对系统进行及时监控。
4. 【强制】对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方
式。
说明:logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,
会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
正例:(条件)
if (logger.isDebugEnabled()) {
    
    
logger.debug("Processing trade with id: " + id + " symbol: " + symbol);
}
正例:(占位符)
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
5. 【强制】避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
正例:<logger name="com.taobao.dubbo.config" additivity="false">
6. 【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过
关键字 throws 往上抛出。
正例:logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);
7. 【推荐】谨慎地记录日志。生产环境禁止输出 debug 日志;有选择地输出 info 日志;如果使
用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘
撑爆,并记得及时删除这些观察日志。
说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请
思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
8. 【参考】可以使用 warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适
从。注意日志输出的级别,error 级别只记录系统逻辑出错、异常等重要的错误信息。如非必
要,请不要在此场景打出 error 级别。

参考:
阿里巴巴Java开发手册v1.2.0.pdf
https://github.com/sunwu51/notebook/blob

如果你觉得对你有帮助,请帮我点个赞 (:

猜你喜欢

转载自blog.csdn.net/liuge36/article/details/110499446