最近,新开发的一个项目遇到了一个log4j的配置问题,之前一直没怎么关注过日志框架,借助这个机会,好好了解下Java的日志框架,便于以后更好的使用。
本文重点介绍了:Java日志框架生态、Java日志框架的结构组成、Slf4j替代Commons Logging。
Java常用的日志框架如下:
- Commons Logging:Apache基金会所属的项目,是一套Java日志接口;
- Slf4j:是一套简易Java日志门面,本身并无日志的实现。(Simple Logging Facade for Java,缩写Slf4j);
- log4j:Apache Log4j是一个基于Java的日志记录工具;
- log4j 2:Apache Log4j 2是apache开发的一款Log4j的升级产品,不兼容log4j 1;
- Logback:一套日志组件的实现(slf4j阵营);
- Jul:Java Util Logging,自Java1.4以来的官方日志实现。
现今,Java日志领域被划分为两大阵营:Commons Logging阵营和SLF4J阵营。
各日志框架之间的关系:
- Commons Logging和Slf4j是两个日志门面框架,系统不和日志实现耦合,日志门面作为接待员,方便日志实现的替换(如:log4j2替换log4j1);
- Commons Logging和log4j1、2配合使用,Slf4j和Logback组合使用;
- 新项目建议使用Slf4j与Logback组合;
桥接原理分析
桥接器原本语境是使用在网络中数据包的转发,在日志框架里,主要是日志门面寻找日志实现类的类。
Slf4j在获取日志框架实现时扫描class path,寻找org.slf4j.impl.StaticLoggerBinder(有多个,会打印警告日志,并选择遇到的第一个),桥接器就是提供桥接来实现类和接口类之间的适配。
对于Commons Logging,其寻找Logger实现的步骤如下:
- 首先,寻找org.apache.commons.logging.LogFactory 属性配置
- 否则,利用JDK1.3 开始提供的service 发现机制,会扫描classpah 下的META-INF/services/org.apache.commons.logging.LogFactory 文件,若找到则装载里面的配置,使用里面的配置。
- 否则,从Classpath 里寻找commons-logging.properties ,找到则根据里面的配置加载。
- 否则,使用默认的配置:如果能找到Log4j 则默认使用log4j 实现,如果没有则使用JDK14Logger 实现,再没有则使用commons-logging 内部提供的SimpleLog 实现。
Slf4j和Commons Logging获取Logger的区别:
Slf4j扫描classpath获取StaticLoggerBinder,通过StaticLoggerBinder的静态绑定逻辑获取Logger。
Commons Logging是采用ClassLoader动态的获取Logger,在一些情况下会产生ClassLoader的问题(如OSGI,主要原因在文末Ceki Gülcü的一片参考文献有提及)
Slf4j&Logback的优势
- Slf4j的静态绑定实现机制决定了其更加通用;
- Logback拥有更好的性能;
Logback声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在Logback中需要3纳秒,而在Log4J中则需要30纳秒。LogBack创建记录器(logger)的速度也更快:13毫秒,而在Log4J中需要23毫秒。更重要的是,它获取已存在的记录器只需94纳秒,而Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的。 - 自动重新加载配置文件,当配置文件修改了,Logback-classic能自动重新加载配置文件
...
LogBack结构
Java日志框架由3部分组成,分别是Logger,Formatter,Appender。
Logger:用来接收用户输入的内容;
Formatter:用来格式化日志内容;
Appender:将日志内容输出到Console,socket,文件,数据库,邮件等。
在Logback中,则分别对应于Logger、Layout、Appender。
-
Logger
Logger有三点需要重点关注:名字属性、Level属性,如何获取。
(1)每个Logger都有一个名字,并且有父子、子孙层次关系;
存在一个特殊的Logger实例,它的名字为“org.slf4j.Logger.ROOT_LOGGER_NAME”,即“ROOT”(2)Logger实例应该设置Level属性,如果某个Logger实例的Level属性未设置,那么沿着Logger实例的层次关系向上回溯直到最顶层的"ROOT"Logger实例为止。
(3)在Logback运行的时候,LoggerContext类实例会维护一个类型为Map<String, Logger>的map对象loggerCache,它的key为Logger实例的名字,它的value为对应的Logger实例。
当我们执行"LoggerFactory.getLogger('xxx')"语句,来检索名字为"xxx"的Logger实例时,LoggerContext类实例会先去查看loggerCache对象,看是否已经存在名字为"xxx"的Logger实例,如果存在,直接返回;否则,先创建好Logger实例(注意这里创建Logger实例的时候,会把loggerCache中不存在的祖先和父亲Logger实例都创建好)并放入loggerCache中,最后返回刚创建好的Logger实例。
即在Logback运行的时候,相同名字的Logger实例只保存一份。 Layout
绑定在Appender上,用来格式化Appender的输出Appender
Appender代表日志输出目的地,可以是Console, File, Sockets, DataBase等等。
一个Logger实例上可以绑定0到多个Appender实例,当在该Logger实例上产生的日志记录请求是有效的情况下,日志记录请求会被发送到所有绑定在该Logger实例上的Appender实例。
一个Logger实例上绑定的Appender实例不仅来自自身的绑定,也来自祖先和父亲Logger实例的Appender绑定,即可以继承祖先和父亲Logger实例绑定的Appender实例。
Slf4j替代Commons Logging
Java生态有许多日志工具,不同的组件可能会使用不同的日志框架,为了不对日志框架产生依赖,Apache引入了Commons Logging门面框架,不过当程序规模越来越庞大时,JCL的动态绑定并不是总能成功。Slf4j的静态绑定功能解决了这一问题,然而,依赖的组件中可能会有使用了Commons Logging的组件,Slf4j提供了jcl-over-slf4j.jar ,可以借助jcl-over-slf4j.jar 讲Commons Logging输出的日志引入到Slf4j中。
Component(服务)
| |
log to Apache Commons Logging(JCL)
V
jcl-over-slf4j.jar — (redirect) —> SLF4j —> slf4j-log4j12-version.jar —> log4j.jar —> 输出日志
(另外也可以删除所有Commons Logging的依赖,不过这太繁琐了,也容易出问题)。
参考文献:
https://www.cnblogs.com/chenhongliang/p/5312517.html(各日志框架介绍)
https://www.cnblogs.com/crazyrunning/p/6145890.html(日志门面的作用)
http://www.runoob.com/design-pattern/facade-pattern.html(门面模式)
https://blog.csdn.net/jpf254/article/details/80757041(Slf4j桥接原理)
http://singleant.iteye.com/blog/934593(commons-logging Logger实现加载步骤)
https://blog.csdn.net/dslztx/article/details/47450741(Logback详解)
https://articles.qos.ch/classloader.html(Ceki Gülcü控诉了Commons Logging的弊端😂)
https://blog.csdn.net/zbajie001/article/details/79596109(Logback的优点)
https://blog.csdn.net/javaloveiphone/article/details/52486257(Slf4j替代Commons Logging)