Java的Log技术
概念理解
1 | 日志门面:一般采取facade设计模式(外观设计模式:外观模式定义了一个高层的功能,为子系统中的多个模块协同的完成某种功能需求提供简单的对外功能调用方式,使得这一子系统更加容易被外部使用)设计的一组接口应用。 |
日志实现底层基本组成如下:
1 | Loggers:Logger负责捕捉事件并将其发送给合适的Appender。 |
实现:当Logger记录一个事件时,它将事件转发给适当的Appender。然后Appender使用Layout来对日志记录进行格式化,并将其发送给控制台、文件或者其它目标位置。另外,Filters可以让你进一步指定一个Appender是否可以应用在一条特定的日志记录上。在日志配置中,Filters并不是必需的,但可以让你更灵活地控制日志消息的流动。
主流的Log技术
日志门面 commons-logging,slf4j
日志实现 log4j,jdk-logging,logback,log4j2
这也符合Java的面向对象设计理念,将接口与实现相分离。
日志门面系统的出现其实已经很大程度上缓解了日志系统的混乱,很多库的作者也已经意识到了日志门面系统的重要性,不在库中直接使用具体的日志实现框架。slf4j作为现代的日志门面系统,已经成为事实的标准,并且为其他日志系统做了十足的兼容工作。
我们能做的就是选一个日志实现框架。logback,log4j2是现代的高性能日志实现框架
log4j 和 log4j2
log4j是Apache的一个开源项目,log4j2和log4j是一个作者,只不过log4j2是重新架构的一款日志组件,他抛弃了之前log4j的不足,以及吸取了优秀的logback的设计重新推出的一款新组件。
log4j是通过一个.properties的文件作为主配置文件的,而现在的log4j 2则已经弃用了这种方式,采用的是.xml,.json或者.jsn这种方式来做,可能这也是技术发展的一个必然性,毕竟properties文件的可阅读性真的是有点差。
1 | <dependency> |
不需要再依赖第三方的技术
JCL(Jakarta Commons Logging)
是apache公司开发的一个抽象日志通用框架,本身不实现日志记录,但是提供了记录日志的抽象方法即接口(info,debug,error…….)。
1 | <dependency> |
JCL不直接记录日志,通过第三方记录日志:如果没有log4j的依赖情况下用JUL(JUL是jdk自带的日志实现)。如果有了log4j则使用log4j。
通过查看源码得知,底层通过一个数组存放具体的日志框架的类名,然后循环数组依次去匹配这些类名是否在程序中被依赖了,如果找到被依赖的则直接使用。默认有四种:
1 | classesToDiscover = {String[4]@513} |
循环加载类 获取Class:
成功过的到直接返回Class
JUL
java自带的一个日志记录的技术,直接使用。
1 | public static void main(String[] args) throws IOException { |
JUL日志等级划分(优先级递减)及内置代表的整数如下:
1 | OFF(Integer.MAX_VALUE) |
当为 Logger 指定了一个 Level, 该 Logger 会包含当前指定级别以及更高级别的日志,logger默认的级别是INFO,比INFO更低的日志将不显示。JUL的默认配置文件loging.properties,该配置文件位于jdk安装目录的lib包下。
如果要更改logger的输出等级,可以通过修改对应配置项的level等级,也可以通过代码的方式去动态设置logger的等级。
1 | log.setLevel(Level.ALL);//设置logger的日志级别为全部,默认输出所有级别日志信息 |
Handler
JUL中用的比较多的是两个Handler类:ConsoleHandler和FileHandler,其中,ConsoleHandler是对控制台输出的默认处理类,FileHandler是对文件输出的默认处理类
1 | log.setUseParentHandlers(false); //禁用日志原本处理类 |
如果没有 log.setUseParentHandlers(false); 父Handler与子Handler都会生效,此时会输出两遍日志内容
自定义Handler
1 | public class MyHandler extends Handler { |
Formatter
默认输出是xml格式,修改显示方式。
1 | public class MyFormate extends Formatter { |
slf4j
slf4j不记录日志,通过绑定器绑定一个具体的日志记录来完成日志记录。slf4j是门面模式的典型应用
引入包:
1 | <dependency> |
测试运行
1 | public class SLF4j { |
报错:
1 | SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". |
这是因为SLF4J的绑定器没有一个具体的日志处理实现日志功能,如果再引入一个绑定包,如:log4j的绑定 即可。
1 | <dependency> |
slf4j实现原理
Logger log = LoggerFactory.getLogger(SLF4j.class);
主要是通过这句代码去拿具体的实现,获取log对象的源码:
1 | public static Logger getLogger(Class<?> clazz) { |
绑定日志框架:
1 | private final static void bind() { |
编译期间,编译器会选择其中一个StaticLoggerBinder.class进行绑定,这个地方sfl4j也在reportActualBinding方法中报告了绑定的是哪个日志框架:
1 | private static void reportActualBinding(Set<URL> binderPathSet) { |
最后得到日志对象:
1 | public static Logger getLogger(String name) { |
桥接器会调用的日志框架实现的相关代码生成其内部的Logger(此Logger与org.slf4j.Logger)不兼容,再通过适配器包装日志框架实现内部的Logger。
日志系统桥接器
我们在项目中一般不直接使用日志实现框架,而是使用外观模式:日志门面组件+桥接器+日志实现框架,这样即使项目更换日志种类,只需更换桥接器和日志实现框架,也就是只更换Jar包就可以了,代码无需做任何改动。
1 | 日志系统桥接器说白了就是一种偷天换日的解决方案。 |
想想一下:
如果log4j -> slf4j,slf4j -> log4j两个桥接器同时存在会出现什么情况?互相委托,无限循环,堆栈溢出。
slf4j -> logback,slf4j -> log4j两个桥接器同时存在会如何?
两个桥接器都会被slf4j发现,在slf4j中定义了优先顺序,优先使用logback,仅会报警,发现多个日志框架绑定实现;
但有一些框架中封装了自己的日志facade,如果其对绑定日志实现定义的优先级顺序与slf4j不一致,优先使用log4j,那整个程序中就有两套日志系统在工作。
log4j2桥接器由log4j2提供,其他桥接器由slf4j提供。
官方桥接案例说明:
详细说明这三个案例
左上图
1 | 现状:目前的应用程序中已经使用了如下混杂方式的API来进行日志的编程: |
右上图
1 | 现状:目前的应用程序中已经使用了如下混杂方式的API来进行日志的编程: |
左下图
1 | 现状:目前的应用程序中已经使用了如下混杂方式的API来进行日志的编程: |
logback
simple-log
各种jar包总结
1 | log4j1: |
slf4j绑定实际的日志框架
1 | 使用slf4j的API进行编程,底层想使用log4j1来进行实际的日志输出 |
日志框架转向slf4j
1 | 使用log4j1的API进行编程,但是想最终通过logback来进行输出,所以就需要先将log4j1的日志输出转交给slf4j来输出,slf4j再交给logback来输出。日志框架之间的切换 |
冲突说明
jcl-over-slf4j 与 slf4j-jcl 冲突
1 | jcl-over-slf4j: commons-logging切换到slf4j |
如果这两者共存的话,必然造成相互委托,造成内存溢出
log4j-over-slf4j 与 slf4j-log4j12 冲突
1 | log4j-over-slf4j : log4j1切换到slf4j |
如果这两者共存的话,必然造成相互委托,造成内存溢出。但是log4j-over-slf4内部做了一个判断,可以防止造成内存溢出:
即判断slf4j-log4j12 jar包中的org.slf4j.impl.Log4jLoggerFactory是否存在,如果存在则表示冲突了,抛出异常提示用户要去掉对应的jar包,代码如下,在slf4j-log4j12 jar包的org.apache.log4j.Log4jLoggerFactory中:
1 | static { |
jul-to-slf4j 与 slf4j-jdk14 冲突
1 | jul-to-slf4j : jdk-logging切换到slf4j |
如果这两者共存的话,必然造成相互委托,造成内存溢出