日志在工作中起到关键作用,我们经常使用它来打印关键信息,方便分析,或者是输出错误信息,用于bug排查,spring中同样使用了日志进行信息的输出,但是spring4和spring5之间的日志又有些不同,接下来我们就进行一些分析。
1. 各种日志技术简述:
log4j,jul,jcl,log4j2,slf4j
我们先把他们展示出来,以免引用错误。
1.1 log4j
使用log4j需要引入log4j的配置文件log4j.properties,内容简配一下:
log4j.rootLogger = info,stdout
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
pom文件中只引入log4j所需要的jar包
程序也写的简单一点,输出日志就行:
结果:
日志是输出的。
1.2JUL
JUL 是jdk自带的,所以pom文件中不需要引入任何包,直接撸代码:
结果:
日志输出格式我们不再做改变,记住log4j和jul的日志输出格式,后面有用到。
1.3:JCL
pom中引入所需要的包:
撸代码:
结果:
呜呼,这个日志输出是不是很眼熟呀,长得很像上面JUL打印出来的日志,难道他们之间有什么不可描述的事情?所以最帅的我准备打开JCL的源码一探究竟。
从上面代码中的LogFactory的getLog方法下手,进去:
木得意思,就一行代码,那就再深一点,进入getInstance()中,
第一行代码看着像是拿到了log,不过老哥我已经帮大家踩过坑了,debug走了一遍,第一行代码执行后,Log 依然是null,所以我们继续往下走,进入到
this.newInstance()方法中,有点长,就酱紫:
protected Log newInstance(String name) throws LogConfigurationException { Log instance = null; try { Object[] params; if (this.logConstructor == null) { instance = this.discoverLogImplementation(name); } else { params = new Object[]{name}; instance = (Log)this.logConstructor.newInstance(params); } if (this.logMethod != null) { params = new Object[]{this}; this.logMethod.invoke(instance, params); } return instance; } catch (LogConfigurationException var5) { throw var5; } catch (InvocationTargetException var6) { Throwable c = var6.getTargetException(); if (c != null) { throw new LogConfigurationException(c); } else { throw new LogConfigurationException(var6); } } catch (Throwable var7) { throw new LogConfigurationException(var7); } }
这段代码差一点就比我的长处还长呢,所以我又帮大家debug了一遍,在第一个if判断 之后执行的这个方法 this.discoverLogImplementation(name);就是我们要找的,再深入,在
discoverLogImplementation()这个方法中,我找到了这段代码:
result属性就是我们需要返回的Log,循环条件中有一个classesToDiscover,并且还把他的值传给了this.createLogFromClass()方法,点击看看他是什么,
String[] classesToDiscover = new String[]{"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"};
这个数组里面找到了 org.apache.commons.logging.impl.Jdk14Logger,这不就是我们的JUL 吗?再想到刚刚的for循环,我们很容易理解,JCL就是循环这个数组,一次获取Log,知道Log 不为null为止,为了验证我们的猜想,
我们可以引入log4j的jar包和配置文件,其他代码不做修改,这样按照数组顺序,应该打印出来log4j的日志。
果然,在有log4j的情况下,JCL按照刚刚那个数组,顺序加载log,知道Log不为null。
由此可以看出,JCL起到的是一个中间商赚差价的作用,在有log4j的时候使用log4j,否则使用JUL
2 Sring4 的日志体系
上面说了那么多,只是介绍了一部分日志技术,接下来我们开始分别分析spring4和spring5的日志体系。
2.1 构建spring4项目
采用java+注解的方式快速构建,pom中只引入spring-context包
运行下面的代码,可以看到有日志输出
找到打印日志的地方,debug模式下,查看输出日志的Log是什么log
可以看出是jdk14Logger,这个在JCL中说过,这个指的是JUL,也就是说在默认spring日志体系下,采用的是JUL,
接下来,我们按照之前的方法引入log4j,debug运行上面的程序,再次查看日志类型
额,这次在增加log4j jar包和配置文件的情况下,spring4有使用了log4j,这么像JCL呢,木错,让我们在idea中打开spring4的日志依赖结构:
common-logging 这不就是JCL使用到的包吗,可以看出,Spring4使用的是原生的JCL,所以在有log4j的时候使用log4j打印日志,没有的时候使用JUL打印日志。
3.Spring5日志体系
线上依赖结构图:
答题结构没变,只是原来common-logging ,换成了spring-jcl,看名字就知道是spring自造的包,jcl,更是标注了,它使用的是JCL日志体系。
所以还是看源码吧。
按照之前的经验,我们只用debug找到spring内部一个Log,看看他的产生方式和类型。这次我给大家找了AbstractApplicationContext里面找到产生Log的地方
进入这个方法的getLog()中,一直深入,不要怜惜spring,找到LogAdapter中的createLog()方法
可以看出来spring5中对日志的生产,不在像原生JCL中那样使用一个数组,然后进行循环产生,这里用到的是Switch case,这个关键字段LogApi又是在哪一部分赋值的呢?看图
Duang ,没错是在静态代码块中赋的值,为了验证,我们准备用其中提到的log4j2验证(注意:log4j不行,因为这里的switch没有log4j选项),首先我们准备log4j2的配置文件
<Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> Console> Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="Console"/> Root> Loggers> Configuration>
然后准备pom
代码还是这一行,直接运行:
结果有日志打印出来了
所以,在spring5中,依然使用的是JCL,但是不是原生的,是经过改造的JCL,默认使用的是JUL,而原生JCL中默认使用的是log4j.
好了,就酱紫,求轻拍,大家中秋快乐。