你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

slf4j,log4j,logback的区别和相关用法

2021/12/30 20:49:49

slf4j,log4j,logback的区别和相关用法

0.背景

最近出现了log4j2.x低版本漏洞导致JNDI注入问题,于是趁此机会,研究了下目前市面上主要的一些日志框架。其中一些优秀的代码设计思想及在日志系统中的实践值得深入学习,下面是我的一些浅见。

1.JAVA日志体系

  • System.out,System.err: JAVA自身类库
  • log4j1.x,JUL: 在Apache的时候,Ceki参与设计的日志实现框架。Java Util Logging 借鉴log4j1.x JDK1.4推出的JAVA自带的日志实现
  • JCL: Java Common Logging Java 自身的日志门面框架
    ------------ Ceki 离开Apache -----------
  • Slf4j: Ceki借鉴JCL自己开发的日志门面框架
  • logback: Ceki为Slf4j接口设计的默认日志实现框架
  • log4j2: Apache借鉴Slf4j+logback进行开发的项目

有一张图可以来说明 Ceki开发的框架们和Apache组织开发的日志框架:
在这里插入图片描述

2.Slf4j

2.1 Slf4j要解决的问题
面向接口编程,而不是面向实现编程。所以我们应该是按照一套统一的API来进行日志编程,实际的日志框架来实现这套API,这样的话,即使更换日志框架,也能够做到无缝切换。

2.2 Slf4j概述
SLF4J,即简单日志门面(Simple Logging Facade for Java),并不是具体的日志解决方案,它只服务于各种各样的日志系统。按照官方的说法,SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统,不过他也有其默认实现的日志系统也就是后文要介绍的logback。
除了核心的slf4j-api之外,还有slf4j-log4j12slf4j-jdk14等项目。这一类项目统称桥接器项目,针对不同的日志框架有不同的桥接器项目。
在使用logback日志框架时,并没有针对的桥接器,这是因为logback与slf4j都是Ceki所写,在logback中直接实现了slf4j的SPI机制。
2.3 Slf4j解决问题的手段就是桥接各种日志框架
什么是桥接: 桥接是用于把抽象化与实现化解耦,使得二者可以独立变化。将抽象部分与实现部分分离,使它们都可以独立的变化。
主要解决的是在业务有多种变化的情况下,用继承的方式会造成【类爆炸】问题,扩展起来不灵活。
桥接的方式: 应用通过 slf4j-api 的接口调用过来时,桥接类实际会调用其底层的实现,达到一个桥接的过程。
要切换日志框架,只需替换类路径上的slf4j绑定。例如,要从java.util.logging切换到log4j,只需将slf4j-jdk14-1.7.27.jar替换为slf4j-log4j12-1.7.27.jar即可。Slf4j的Logger接口的方法通过Log4jLoggerAdapter进行包装和转换,交由log4j的Logger去执行,这就达到了连接slf4j-api和log4j的目的。
Log4jLoggerAdapter的作用: 该类实现了slf4j的Logger接口,便拥有了其对应的接口能力,同时通过委派关系(构造方法传入)连接到log4j的Logger,在实现slf4j的Logger的方法时,调用log4j的Logger对应的实现。
这是Slf4j桥接到各种目标日志框架的说明:
在这里插入图片描述
2.4 Slf4的适配器们
实现桥接需要用到各个日志框架的适配器,它的主要作用是代理各接口与实际处理 logger 的任务。从外部看起来都是相同的方法,但是内部却是各自来实现自己的功能。需要说明的是,使用Slf4j + logback 这个组合的时候是不需要适配器的,只需要用 slf4j-api即可,原因是logback是Slf4j的默认实现。

  • jcl-over-slf4j.XXX.jar:把 commons-logging 的处理交给 slf4j 来处理
  • jul-to-slf4j.XXX.jar:把 java.util.logging 的处理交给 slf4j 来处理
  • log4j-over-slf4j.XXX.jar:把 log4j 的处理交给 slf4j 来处理
    这些适配器可以分为两类,短横线中是overto的,over类的适配器使用很简单,直接替换目标日志框架即可,因为
    这类jar包的结构和短横线前的日志框架一模一样,完全实现了短横线前日志框架所需要调用的api;而to类的适配器,则是通过SLF4JBridgeHandler,“拦截”JUL日志语句并将它们路由到 SLF4J。但是仍旧需要通过JUL的配置文件进行配置,Slf4j绑定器(如logback)上设置的日志级别等价于JUL handler上的日志级别。
    2.5 可能会遇到的堆栈溢错误
    举个例子:先用 over 库把 log4j 转成了 Slf4j 调用。紧接着,又把 Slf4j 适配到 log4j 上,这就构成了一个死循环,肯定是会出现堆栈溢出的问题的。所以,一个工程里面,只能保留一个日志实现库,还有配套的桥接库,加上其他日志的 over 库。
    2.6 写法
    JCL写法:
int num = 1;
p.setNum(num);
log.info("Set num" + score + " for Person " + p.getName() + " ok.");
SLf4j写法:
int num = 1;
p.setNum(num);
log.info("Set num {} for Person {} ok.", score, p.getName());

可以看到,SLF4J的日志接口传入的是一个带占位符的字符串,用后面的变量自动替换占位符,所以看起来更加自然。
2.7 Slfj运行时序图
SLF4J能够抽象各种具体日志框架,是由StaticLoggerBinder类完成。剧吐时序图如下:(关于Slf4j源码剖析可以看篇文章,时序图也是出自于此 Slf4j源码解析)
在这里插入图片描述
StaticLoggerBinder的具体作用
1.查找类路径下所有的StaticLoggerBinder类
2.如果存在多个StaticLoggerBinder类,则打印日志
3.获取StaticLoggerBinder实例,如果不存在,则抛出NoClassDefFoundError异常
4.打印实际使用StaticLoggerBinder类

3.log4j1.x,log4j2.x以及logback

3.1 log4j1.x的主要组件

Logger: 可以定义多个logger,不同的logger之间可以有继承关系;一个logger可以指定一种日志级别和多个appender(可以同时将日志输出到控制台和文件);
Appender: 指定日志信息存放到什么地方,log4j支持多种appender(console、file…),不同类型的appender可以设置不同的策略。
Layout: 是对日志行格式的抽象。包括HTMLLayout,PatternLayout,SimpleLayout,TTCCLayout。
Level: 日志级别:ALL < DEBUG < INFO < WARN < ERROR < FATAL < OFF 级别越低输出的信息越多。
log4j1.x运行的工作如下:
获取Logger实例–>判断Logger实例对应的日志记录级别是否要比请求的级别低–>若是调用forceLog记录日志->创建LoggingEvent实例–>将LoggingEvent实例传递给Appender–>Appender调用Layout实例格式化日志消息–>Appender将格式化后的日志信息写入该Appender对应的日志输出中。
获取Logger实例–>判断Logger实例对应的日志记录级别是否要比请求的级别低–>若是调用forceLog记录日志–>创建LoggingEvent实例–>将LoggingEvent实例传递给Appender–>Appender调用Layout实例格式化日志消息–>Appender将格式化后的日志信息写入该Appender对应的日志输出中。
下面是其运行时序图:
在这里插入图片描述
3.2 log4j1.x的使用
引入包: gardle是这样:

implementation group: 'log4j', name: 'log4j', version: '1.2.17'

配置 log4j.properties :具体略。
代码中使用:

private static org.apache.log4j.Logger logger = org.apache.log4j.LogManager.getLogger(TestLog.class);

3.3 当老大的log4j2
log4j2与log4j1发生了很大的变化,不兼容。log4j1只是作为一个实际的日志框架,而log4j2的主要变化是想作为一个Slf4j那样的日志门面,统一大家了。他的主要解决方案是两个包:

  • log4j-api: 作为日志接口层,用于统一底层日志系统
  • log4j-core : 作为上述日志接口的实现,是一个实际的日志框架

3.4 log4j2.x的使用
引入包: gardle是这样:

implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.1'
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.17.1'

配置 log4j2.xml:具体略。目前log4j2只支持xml json yuml,不再支持properties文件
代码中使用:

private static final Logger logger=LogManager.getLogger(Log4j2Test.class);

3.5 logback概述

  • 解决的问题: Slf4j每次使用都需要桥接包来和日志实现组件建立关系
  • 解决办法:用logback作为Slf4j接口的默认实现,是log4j1.x的加强版
  • 优点:
    1.内核重写、测试充分、初始化内存加载更小
    2.文档齐全
    3.logback当配置文件修改了,支持自动重新加载配置文件,扫描过程快且安全,它并不需要另外创建一个扫描线程
    4.支持自动去除旧的日志文件,可以控制已经产生日志文件的最大数量
    loback加载较之log4j1.x加载速度更快的原因之一是减少了配置文件的扫描,比如:
    在logback加载时,在系统配置文件System Properties中寻找是否有logback.configurationFile对应的value
    在classpath下寻找是否有logback.groovy(即logback支持groovy与xml两种配置方式);
    在classpath下寻找是否有logback-test.xml;
    在classpath下寻找是否有logback.xml; 以上任何一项找到了就不进行后续扫描
    3.6 logback各模块作用
  • logback-core: 核心代码模块
  • logback-classic:log4j1.x的一个优化版本,实现了slf4j接口,便于切换其他日志组件
  • logback-access: 访问模块,与Servlet容器集成提供Http来访问日志的功能

logback也没有提供任何的 Logger 实现,logback-core 的功能主要就是实现了很大的 appender ,pattern 等,一般我们需要引入logback-classic , 它直接依赖了 logback-core,Slf4j-api 。

3.7 logback的使用

  • 引入包: gardle是这样:
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.10'
  • 配置 logback.xml:具体略
  • 代码中使用:
private static Logger logger = LoggerFactory.getLogger(TestLog.class);

4.总结,区别和联系

  • Slf4j: 日志门面框架,桥接各类日志框架。
  • log4j 1.x: 日志实现框架,加载速度较慢。
  • log4j 2.x: 不兼容log4j 1.x,简化了log4j1.x的配置,具有强大的异步性能,分为api和实现两部分。
  • logback: 日志实现框架,充分测试,加载快。

最后附上参考文章链接:
https://www.choupangxia.com/2020/11/28/slf4j-4/
https://www.jianshu.com/p/bc2cfa14e99b