日志概览
市面日志框架
JCL
JCL即Jakarta Commons Logging 后进入apache 又名 Apache Commons Logging
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
JUL
JUL即 jdk自带的logging 以java.util.logging.Logger为接口的日志组件
log4j1
其仅仅是一个实现的日志框架,并非门面模式的接口
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
即log4j1框架,使用配置文件为log4j.properties
log4j2
log4j团队做的门面模式框架,但是和log4j1并不兼容,有slf4j、commons-logging门面。
其使用配置文件为 log4j2.xml(xml、yml、json)
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
logback
其依赖jar,官方使用方式中即和slf4j集成
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
使用配置文件 logback.groovy>logback-test.xml>logback.xml
配置文件中只有logback.configurationFile的系统属性是可以更改的,
System.setProperty("logback.configurationFile", "/path/to/config.xml");
slf4j
会在jar包中找org/slf4j/impl/StaticLoggerBinder.class 类,如果找到多个,则随机选取一个。选取的会打印在日志中。然后会拥有ILoggerFactory的实现来走实际的日志实现框架。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
jar总结
log4j1:
log4j:log4j1的全部内容
log4j2:
log4j-api:log4j2定义的API
log4j-core:log4j2上述API的实现
logback:
logback-core:logback的核心包
logback-classic:logback实现了slf4j的API
commons-logging:
commons-logging:commons-logging的原生全部内容
log4j-jcl:commons-logging到log4j2的桥梁
jcl-over-slf4j:commons-logging到slf4j的桥梁
slf4j转向某个实际的日志框架:
slf4j-jdk14:slf4j到jdk-logging的桥梁
slf4j-log4j12:slf4j到log4j1的桥梁
log4j-slf4j-impl:slf4j到log4j2的桥梁
logback-classic:slf4j到logback的桥梁
slf4j-jcl:slf4j到commons-logging的桥梁
某个实际的日志框架转向slf4j:
jul-to-slf4j:jdk-logging到slf4j的桥梁
log4j-over-slf4j:log4j1到slf4j的桥梁
jcl-over-slf4j:commons-logging到slf4j的桥梁
业务系统现状
综上可以看出,我们有
log4j1 的1.2.8版本jar 和log4j-over-slf4j:log4j1到slf4j的桥梁
log4j2 的api及core log4j-jcl jcl到log4j的桥梁 log4j-to-slf4j 即log4j到slf4j的桥梁
slf4j 的api 及 log4j-slf4j-impl:slf4j到log4j2的桥梁 slf4j-log4j12:slf4j到log4j1的桥梁
jcl jcl-over-slf4j:commons-logging到slf4j的桥梁
logback 的集成包和实现包
其中 log4j-slf4j-impl 和 log4j-to-slf4j 都是log4j2的包,是不可以互相使用的,会导致log4j和slf4j的互相桥接,引起内存泄露。但是目前在web模块是引用,但是非同版本。是非常危险的。参照 log4j™2官网:http://logging.apache.org/log4j/2.x/runtime-dependencies.html
从上面可以看出,在2.3版本中,我们目前实际使用的是 其他转slf4j然后由log4j-slf4j-impl 最终由log4j2进行实际的日志输出。
新进日志问题
在引入二方包的SDK中,
二方库中进行了参数打印,并且打印了一个对象。。
升级完二方包后,出现了一个问题,即LOGGER.info 会报 noSuchMethodError异常。
排查方法:
第一
首先对日志包进行了排查和同名类的排查,将依赖中的2.2版本的log4j-api进行了排除,升级到2.3。
无果
第二
我们使用了idea的debug的
方式,进行了日志打印及反射获取到该方法里面的LOGGER并进行打印,都是没有问题的。无果
第三
考虑是否是依赖包发生了桥接问题,从而导致其找不到实际需要的具体实现,于是将项目中关于log4j及slf4j全部依赖进行排除,并引入最新的依赖关系即
<!-- commons-logging到slf4j的桥梁 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<!-- java.util.logging到slf4j的桥梁 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
<!-- log4j1 官方重写至log4j2 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
</dependency>
<!-- log4j2对slf4j的桥接实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</dependency>
<!-- slf4j 核心包 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- log4j2 核心包 start -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
<!-- log4j2 核心包 end -->
但是版本还是为了和项目保持一致,对于log4j2的版本使用了2.3,对slf4j的版本使用了1.7.25.具体参考log4j2的官网 http://logging.apache.org/log4j/2.x/log4j-slf4j-impl/index.html
然后进行了打印当时的logger对象是谁
Class<? extends Logger> clazz = LOGGER.getClass();
String name = clazz.getName();
CodeSource cs = clazz.getProtectionDomain().getCodeSource();
String location = cs.getLocation().getPath();
StringBuilder allMethodName = new StringBuilder();
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String methodName = method.getName();
allMethodName.append("方法名称:").append(methodName);
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> clas : parameterTypes) {
String parameterName = clas.getName();
allMethodName.append("参数名称:").append(parameterName);
}
allMethodName.append("*****************************");
}
System.out.println(String.format("当前使用类【%s】文件位置【%s】类加载器为【%s】,所有方法及参数类型为【%s】", name, location, clazz.getClassLoader(), allMethodName.toString()));
进行测试后无果。
第四
在开始的时候报错为 noSuchMethodError(java.lang.String,java.lang.Object)开始引起我们的注意,由于只有一个参数的在Log4j1中出现过,于是有了上面的桥接器。但是我们知道对于2.3版本的Logger接口
方法名称:info参数名称:java.lang.String参数名称:[Ljava.lang.Object;
其中的第二个参数产生了怀疑。
第二个参数为可变长对象
引申一下可变长对象的一些东西
1 在调用方法的时候,如果能够和固定参数的方法匹配,也能够与可变长参数的方法匹配,则选择固定参数的方法。看下面代码的输出:
2 如果要调用的方法可以和两个可变参数匹配,则出现错误
3 一个方法只能有一个可变长参数,并且这个可变长参数必须是该方法的最后一个参数
public static void m1(String s, String... ss) {
for (int i = 0; i < ss.length; i++) {
System.out.println(ss[i]);
}
}
public static void main(String[] args) {
m1("");
m1("aaa");
m1("aaa", "bbb");
}
此时开始锁定到二方包的class里面应该是被编译成了第二个参数为Object,也就是二方包里面的Logger是存在一个方法为
的方法,于是我们分析了他里面的包,将java文件拿出来做出来class进行了查看,利用jdk自带的工具
javap -verbose class文件
从而发现他们的class文件真的就是
于是锁定问题所在
依赖方打包时使用了高版本的编译成的class,在实际运行时,lib中只有我们系统低版本的2.3的接口,其中确实没有类似的接口。idea在进行调用时,由于是自己本地包,编译的本地class(可理解为不同于二方包的class植入,例如阿里测试工具那样)所以也没有问题。
为了验证,我们让依赖方打一个显示依赖日志jar的快照包
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-jcl</artifactId>
<version>2.3</version>
</dependency>
然后进行测试,发现是没有问题的。
同样我们将本项目包升级至
<slf4j.new.version>1.7.5</slf4j.new.version>
<log4j.version>2.11.2</log4j.version>
<!-- commons-logging到slf4j的桥梁 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- java.util.logging到slf4j的桥梁 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<!-- log4j2对slf4j的桥接实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</dependency>
<!-- log4j1到slf4j的桥接器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
同样可以进行运行,其中slf4j的版本升级是由于1.7.25无法适配2.11.2,依赖方使用的slf4j为1.7.29,且slf4j官网
http://www.slf4j.org/manual.html 其中强烈建议使用者使用1.7.5或者以后版本。
待决策项
1.日志是否进行升级至2.11.2和1.7.5版本。具体对比详见上述情况
2.日志是否直接进行显示依赖,并排除其他二方三方间接依赖
思考
1.提供出去的jar,是否有必要打印日志。日志打印的一些注意事项
2.提供出去的jar,是否需要遵循最简单化原则。例如是否需要引入三方包,甚至二方包
3.接收到别人的jar,是否需要进行强制排除,从而确保不会受到相关影响例如
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>