Java Log 简介
[TOC]
初衷及目的
初衷
为什么项目里会引入这么多关于log的jar包?
都是干嘛吃的?
日志圈真乱!!!
1 | commons-logging-1.2.jar |
目的
- 介绍java日志体系的脉络和关系
- 重点介绍Log4j2的配置
- Log4j2的原理浅析
Java日志体系分为日志门面和日志实现。下面将分别介绍它们。
日志门面
- 代理系统,自身不实现具体的日志打印逻辑。接受日志请求,将真正的打印逻辑交给具体的日志实现系统。
- 可插拔,业务可以通过配置或者加入一些jar包的方式切换到其他日志实现框架,而不需要修改代码逻辑,解耦。
目前主要有两套通用日志门面:common-logging、slf4j。
###1. common-logging:Apache Commons Logging
使用方式:
1 | Log log = LogFactory.getLog(xx.class); |
特点:
- 简单、非常轻的一种桥接(动态查找)方式。
- 限制:不支持OSGI,模块化应用
- 功能单一:只能支持到具体日志系统间的桥接,而不能支持从具体日志系统到其自身的桥接。
###2. SLF4J:Simple Logging Facade for JAVA
使用方式:
1 | Logger logger = LoggerFactory.getLogger(xx.class); |
特点:
- 功能强大,不仅支持到具体日志实现之间的桥接,同时也支持从具体日志系统到自身的桥接
- 支持OSGI模块化应用
- 通过静态绑定的桥接方式
日志实现
###1. Jdk-logging
JDK自带的一种自定义的、可扩展的日志框架(java.util.logging)。但是其API并不完善,不是很很友好,而且对于日志的级别分类也不是很清晰
JDK Logging深入分析
###2. Log4j1
Log4j也是Apache的开源项目,最早被广泛应用的日志解决方案。
- 日志输出目的地可控;
- 输出格式可控;
- 通过日志级别,更细致控制日志的生成过程
- 通过配置文件来灵活地进行配置日志输出,而不需要修改程序代码。
1.x系列在15年8月被Apache宣布停止维护(Apache Blog)
###3. LogBack
也是出自Log4j的创始人,最初的意图是用来替代Log4j。在日志性能、功能、可用性上有了很大程度的提升: Reasons to prefer logback over log4j
Logback当前分为三个模块:logback-core, logback-classic and logback-access;
logback-core模块是其他两个模块的基石;
logback-classic是log4j的改良版,别外,其也实现了SLF4J API,使其可以便捷地更换其他日志实现系统
logback-access与Servlet容器集成,提供通过Http来访问日志的功能
###4. Log4j2
Log4j2也是Apache的开源项目,它对Log4j1做了巨大的提升。并且也提供了很多Logback的改进,并且改进了Logback框架存在的一些问题。
- 性能提升:包含下一代基于the LMAX Disruptor library的异步日志框架。多线程场景下,吞吐量是 Log4j1和Logback的18倍,响应时间也比它们小很多。性能报告:http://logging.apache.org/log4j/2.x/performance.html
- 插件式结构:可以根据需要扩展Log4j2. 可以实现Appender、Logger、Filter
- 动态加载配置:可以动态地加载修改的配置,并且在重配置时,不会丢失日志时间
- 支持多种API,Log4j2自己实现了Log4j1、Common-logging、Slf4j的桥接(log4j-1.2-api、log4j-jcl、log4j-slf4j-impl)
…
桥接
日志门面通过什么方法与具体的日志实现进行绑定。
1.Common-logging 桥接
其通过动态查找的机制,在程序运行时自动找出真正使用的日志库。也是比较粗略的一种方式
其运行原理:
1 | Log log = LogFactory.getLog(xx.class); |
其首先获取一个日志对象的工厂,然后通过工厂来生成对象
1 | return getFactory().getInstance(clazz); |
getFactory()方法中展示了日志工厂的逻辑:
1 | //1.全局系统变量中寻找 org.apache.commons.logging.LogFactory |
获取工厂对象后,就由工厂对象跟据相应的配置来生成日志对象。以默认的工厂类LogFactoryImpl为例,我们来分析生成日志对象的过程:
其核心代码在discoverLogImplementation(String logCategory)方法中:
1 | //1.首先 判断用户是否自定义了日志实现类 org.apache.commons.logging.Log、org.apache.commons.logging.log 为key的用户配置文件项或系统属性 |
总结:
- 首先寻找日志工厂类:
通过检查系统属性、执行路径文件和用户配置文件,看是否定义了相应的日志工程类。若有则加载此类,并通过其来生成日志对象;否则选择默认的日志工厂:org.apache.commons.logging.impl.LogFactoryImpl
- 默认日志工厂获取日志适配器的过程:首先去寻找用户是否自己配置了日志适配器。若有则加载此类并初始化返回;若没有,则按一个固定的顺序来尝试来实例化日志适配器,若实例化成功则返回。
2. SLF4J 桥接
下图展示的是SLF4J和其他实现日志框架的桥接示意图:
可以看到通过jar包和其他日志实现框架起桥梁作用。这些起桥接作用的jar包是怎么起作用的呢?
是因为StaticLoggerBinder
的作用!
从SLF4J的入口来寻找答案:
1 | public static Logger getLogger(String name) { |
performInitialization 初始化日志系统, 通过加载项目中引入的桥接jar包中的StaticLoggerBinder,绑定特定日志实现框架的作用。
1 | private final static void performInitialization() { |
与SLF4J相关的桥接包里都有org/slf4j/impl/StaticLoggerBinder
类,同时所有的StaticLoggerBinder
都实现了LoggerFactoryBinder
接口,方法getLoggerFactory()
用于返回桥接包内的具体工厂对象(这些工厂对象都在StaticLoggerBinder对象的构造函数中进行了初始化),比如Log4jLoggerFactory、JDK14LoggerFactory、JDK14LoggerFactory…
SLF4j官方给出了通过SLF4J桥接其他日志实现框架所依赖的jar。这些包文件中无一例外地包含类StaticLoggerBinder
总结:
- SLF4j是通过
StaticLoggerBinder
来与具体日志实现框架进行桥接的。 - 当SLF4J扫描到有多个
StaticLoggerBinder
的实现时,会发出报警。同时会随机选一个。因此应该尽量杜绝这种情况发生。
另外,SLF4J也给出了在业务代码中也使用了其他日志API的情况下,桥接到SLF4J所依赖的包。
其原理是:在这些包内部(log4j-over-slf4j…)实现相应日志API的类(相同的类名称)。比如log4j-over-slf4j
实现了和log4j1同类名的org.apache.log4j.LogManager
,而在log4j-over-slf4j
的LogManager
里就实现了桥接SLF4J。
3. 补充说Log4j2的桥接
Log4j2的特殊是因为Log4j2在SLF4j和Common-logging等之后诞生,而Log4j2为了适配这些通用日志接口,不得不做配合它们的适配工作。
下图展示的是 通过其他日志接口桥接到日志实现框架Log4j2所需要引入的依赖。
从图中可以看出:
Log4j2为了桥接Common-logging 提供了Log4j-jcl.jar,资源文件:META-INF/services/org.apache.commons.logging.LogFactory内容:
1
org.apache.logging.log4j.jcl.LogFactoryImpl
对应的即为 上文 Common-logging包 getFactory()方法的第二步。
Log4j2为了桥接SLF4j 也提供了log4j-slf4j-impl.jar。其包内容如下:提供了org.slf4j.impl.StaticLoggerBinder.class;以起到桥接的作用。
总结
简化:
1 | commons-logging-1.2.jar X |