Java日志框架

最近,新开发的一个项目遇到了一个log4j的配置问题,之前一直没怎么关注过日志框架,借助这个机会,好好了解下Java的日志框架,便于以后更好的使用。

本文重点介绍了:Java日志框架生态、Java日志框架的结构组成、Slf4j替代Commons Logging。

Java常用的日志框架如下:

  1. Commons Logging:Apache基金会所属的项目,是一套Java日志接口;
  2. Slf4j:是一套简易Java日志门面,本身并无日志的实现。(Simple Logging Facade for Java,缩写Slf4j);
  3. log4j:Apache Log4j是一个基于Java的日志记录工具;
  4. log4j 2:Apache Log4j 2是apache开发的一款Log4j的升级产品,不兼容log4j 1;
  5. Logback:一套日志组件的实现(slf4j阵营);
  6. Jul:Java Util Logging,自Java1.4以来的官方日志实现。

现今,Java日志领域被划分为两大阵营:Commons Logging阵营和SLF4J阵营。

各日志框架之间的关系:

  1. Commons Logging和Slf4j是两个日志门面框架,系统不和日志实现耦合,日志门面作为接待员,方便日志实现的替换(如:log4j2替换log4j1);
  2. Commons Logging和log4j1、2配合使用,Slf4j和Logback组合使用;
  3. 新项目建议使用Slf4j与Logback组合;

桥接原理分析

桥接器原本语境是使用在网络中数据包的转发,在日志框架里,主要是日志门面寻找日志实现类的类。


Java日志框架_第1张图片
log.png

Slf4j在获取日志框架实现时扫描class path,寻找org.slf4j.impl.StaticLoggerBinder(有多个,会打印警告日志,并选择遇到的第一个),桥接器就是提供桥接来实现类和接口类之间的适配。

对于Commons Logging,其寻找Logger实现的步骤如下:

  1. 首先,寻找org.apache.commons.logging.LogFactory 属性配置
  2. 否则,利用JDK1.3 开始提供的service 发现机制,会扫描classpah 下的META-INF/services/org.apache.commons.logging.LogFactory 文件,若找到则装载里面的配置,使用里面的配置。
  3. 否则,从Classpath 里寻找commons-logging.properties ,找到则根据里面的配置加载。
  4. 否则,使用默认的配置:如果能找到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的优势

  1. Slf4j的静态绑定实现机制决定了其更加通用;
  2. Logback拥有更好的性能;
    Logback声称:某些关键操作,比如判定是否记录一条日志语句的操作,其性能得到了显著的提高。这个操作在Logback中需要3纳秒,而在Log4J中则需要30纳秒。LogBack创建记录器(logger)的速度也更快:13毫秒,而在Log4J中需要23毫秒。更重要的是,它获取已存在的记录器只需94纳秒,而Log4J需要2234纳秒,时间减少到了1/23。跟JUL相比的性能提高也是显著的。
  3. 自动重新加载配置文件,当配置文件修改了,Logback-classic能自动重新加载配置文件
    ...

LogBack结构

Java日志框架由3部分组成,分别是Logger,Formatter,Appender。
Logger:用来接收用户输入的内容;
Formatter:用来格式化日志内容;
Appender:将日志内容输出到Console,socket,文件,数据库,邮件等。
在Logback中,则分别对应于Logger、Layout、Appender。

  1. 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的map对象loggerCache,它的key为Logger实例的名字,它的value为对应的Logger实例。
    当我们执行"LoggerFactory.getLogger('xxx')"语句,来检索名字为"xxx"的Logger实例时,LoggerContext类实例会先去查看loggerCache对象,看是否已经存在名字为"xxx"的Logger实例,如果存在,直接返回;否则,先创建好Logger实例(注意这里创建Logger实例的时候,会把loggerCache中不存在的祖先和父亲Logger实例都创建好)并放入loggerCache中,最后返回刚创建好的Logger实例。
    即在Logback运行的时候,相同名字的Logger实例只保存一份。

  2. Layout
    绑定在Appender上,用来格式化Appender的输出

  3. 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)

你可能感兴趣的:(Java日志框架)