类加载器隔离朴实案例(二)logback
背景:与类加载器隔离朴实案例【重点】【loadclass yetdone】(一)相同,避免主项目pom中众多log jar包冲突(比如:java日志组件的关系 slf4j logback log4j ),套路还是一样
现成的代码继承:work log
pom -war
好多log jars与logback.xml等配置文件,jetty/tomcat webclassloader
我的logback jars,JdbcProxyClassLoader
1 首先,项目pom去除依赖,模拟我们的类加载器与webclassloader完全隔离的场景(因为我本机是springboot的关系,主pom必须移除这些jar,否则会受到双亲委派的干扰);借助maven dependengcy:tree 查看maven依赖树(母项目指定pluginManagement)
[INFO] | +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.8.RELEASE:compile
[INFO] | | +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] | | | \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.2:compile
[INFO] | | | \- org.apache.logging.log4j:log4j-api:jar:2.11.2:compile
[INFO] | | \- org.slf4j:jul-to-slf4j:jar:1.7.28:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.1.8.RELEASE:test
[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.1.8.RELEASE:test
[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.1.8.RELEASE:test
[INFO] | +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO] | | +- net.minidev:json-smart:jar:2.3:test
[INFO] | | | \- net.minidev:accessors-smart:jar:1.2:test
[INFO] | | | \- org.ow2.asm:asm:jar:5.0.4:test
[INFO] | | \- org.slf4j:slf4j-api:jar:1.7.28:compile
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </exclusion> </exclusions> </dependency>
使MySpringBoot不再依赖slf logback关键词
2 在自定义类加载器中 加入slf logback相关3个包,并在nativejars放入3个jar包
static {
try {
InputStream inputStream = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/mysql-connector-java-5.1.0-bin.jar");
InputStream inputStreamSlf4jApi = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/slf4j-api-1.7.25.jar");
InputStream inputStreamLogbackCore = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/logback-core-1.2.3.jar");
InputStream inputStreamLogbackClassic = FakeJdbcDriverClassLoader.class.getResourceAsStream("nativejars/logback-classic-1.2.3.jar");
jdbcDriverClassLoader = new FakeJdbcDriverClassLoader(new JarInputStream[]{
new JarInputStream(inputStream),
new JarInputStream(inputStreamSlf4jApi),
new JarInputStream(inputStreamLogbackCore),
new JarInputStream(inputStreamLogbackClassic)});
jdbcDriverClassLoader.configureLogback();【调用】
} catch (Exception e) {
e.printStackTrace();
}
}
3 根据从源码来理解slf4j的绑定,以及logback对配置文件的加载,考虑logback会找classpath下的logback.xml,可能有很多
spring boot,自定义加载器为系统类加载器子,优先从父加载器,也就是主目录的resources目录找logback.xml,因此可以不用这段代码重置环境
jetty/tomcat,自定义加载器与web加载器平行,会直接报找不到logback,除非再弄个jar,里面就包一个logback.xml
我们需要特异性加载:
private void configureLogback() throws Exception {
Class clLoggerFactory = loadClass("org.slf4j.LoggerFactory");
Method getILoggerFactory = clLoggerFactory.getMethod("getILoggerFactory");
Object loggerContext = getILoggerFactory.invoke(null);
Class clLoggerContext = loadClass("ch.qos.logback.classic.LoggerContext");
Method method = clLoggerContext.getMethod("reset");
method.invoke(loggerContext);
Class clJoranConfigurator = loadClass("ch.qos.logback.classic.joran.JoranConfigurator");
Object configurator = clJoranConfigurator.newInstance();
URL url = this.getClass().getClassLoader().getResource("scef-logback.xml");【重点】
method = clJoranConfigurator.getMethod("setContext", loadClass("ch.qos.logback.core.Context"));
method.invoke(configurator, loggerContext);
method = clJoranConfigurator.getMethod("doConfigure", URL.class);
method.invoke(configurator, url);
}
考虑到配置的类在我们自定义加载器中,在主环境ide中没有(因为被我们第1条手动干掉了);或有其他不可靠、引起冲突版本在ide中,全程使用反射
scef-logback.xml放到resources下
非反射版本:https://www.cnblogs.com/zhchoutai/p/8522845.html
File logbackFile = new File("./conf/logback.xml");
if (logbackFile.exists()) {
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(lc);
lc.reset();
try {
configurator.doConfigure(logbackFile);
}
catch (JoranException e) {
e.printStackTrace(System.err);
System.exit(-1);
}
}
4 slf-api包装类
package com.example.demo.testcase;
import java.lang.reflect.Method;
/**
* https://www.cnblogs.com/silyvin/p/12582740.html
* Created by joyce on 2020/3/28.
*/
public class ScefLogbackFactory {
public static ScefLogger getLogger(Class c) {
try {
Class cl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.LoggerFactory");
Method method = cl.getMethod("getLogger", Class.class);
Object log = method.invoke(null, c);
ScefLogger scefLogger = new ScefLogger();
scefLogger.setLogger(log);
return scefLogger;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static class ScefLogger {
public Object getLogger() {
return logger;
}
public void setLogger(Object logger) {
this.logger = logger;
}
Object logger;
public void info(String var1) {
try {
Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
Method info = logcl.getMethod("info", String.class);
info.invoke(this.logger, var1);
} catch (Exception e) {
throw new RuntimeException(e) ;
}
}
public void info(String var1, Object... var2) {
try {
Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
Method info = logcl.getMethod("info", String.class, new Object[0].getClass());
info.invoke(this.logger, var1, var2);
} catch (Exception e) {
throw new RuntimeException(e) ;
}
}
public void debug(String var1, Object... var2) {
try {
Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
Method info = logcl.getMethod("debug", String.class, new Object[0].getClass());
info.invoke(this.logger, var1, var2);
} catch (Exception e) {
throw new RuntimeException(e) ;
}
}
public void debug(String var1) {
try {
Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
Method info = logcl.getMethod("debug", String.class);
info.invoke(this.logger, var1);
} catch (Exception e) {
throw new RuntimeException(e) ;
}
}
public void warn(String var1, Object... var2) {
try {
Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
Method info = logcl.getMethod("warn", String.class, new Object[0].getClass());
info.invoke(this.logger, var1, var2);
} catch (Exception e) {
throw new RuntimeException(e) ;
}
}
public void warn(String var1) {
try {
Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
Method info = logcl.getMethod("warn", String.class);
info.invoke(this.logger, var1);
} catch (Exception e) {
throw new RuntimeException(e) ;
}
}
public void error(String var1, Object... var2) {
try {
Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
Method info = logcl.getMethod("error", String.class, new Object[0].getClass());
info.invoke(this.logger, var1, var2);
} catch (Exception e) {
throw new RuntimeException(e) ;
}
}
public void error(String var1) {
try {
Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
Method info = logcl.getMethod("error", String.class);
info.invoke(this.logger, var1);
} catch (Exception e) {
throw new RuntimeException(e) ;
}
}
}
}
4.22 append:
void error(String var1, Throwable var2) {
try {
Class logcl = FakeJdbcDriverClassLoader.getJdbcDriverClassLoader().loadClass("org.slf4j.Logger");
Method info = logcl.getMethod("error", String.class, Throwable.class);
info.invoke(this.logger, var1, var2);
} catch (Exception e) {
throw new RuntimeException(e) ;
}
}
void error(Throwable var1) {
this.error(var1.getMessage(), var1);
}
5 调用
@Controller
@RequestMapping("/log")
public class LogController {
private static final ScefLogbackFactory.ScefLogger logger = ScefLogbackFactory.getLogger(LogController.class);
@RequestMapping(value = "/get")
@ResponseBody
public String get() {
logger.info("create");
logger.info("create {} df {} {}", "xx", "xx", "xx");
return "true";
}
}
4.22 修改
@Controller
@RequestMapping("/log")
public class LogController {
private static final ScefLogbackFactory.ScefLogger logger = ScefLogbackFactory.getLogger(LogController.class);
@RequestMapping(value = "/get")
@ResponseBody
public String get() {
logger.info("create");
logger.info("create {} df {} {}", "xx", "xx", "xx");
logger.error("create {} dferror {} {}", "xxerror", "xxerror", "xxerror");
try {
int i = 1/0;
} catch (Exception e) {
logger.error(e);
}
return "true";
}
}
6 尽量打包调试,不要ide;避免ide母子兄弟项目所有jar包都在exernal library造成干扰,本次实践,虽然MySringBoot项目经过第1步去除了依赖,但有个平级的兄弟项目MyMain中仍然有引用(java.lang.NoClassDefFoundError类似这种错误catch expection是捕获不到的异常),而且jar包都在exernal library中显示,为避免造成干扰,直接打包后运行
7 在生产级得到验证可行
8 如果log文件没出来,考虑
1)logback.xml没写对,我这一次就是,居然没有fileAppender,只有console,所以只在控制台打出来了;或者有错别字,比如手压到键盘了
2)路径在服务器上没有写入权限
9
4.22日,error info文件分离且跨天调试通过
10 在生产级testcase 打印日志出现问题
自定义类加载的类类型比较:类的相同通过对是否为同一个类加载器进行判断中的4
11 日志统一

参考:从源码来理解slf4j的绑定,以及logback对配置文件的加载
依赖包使用 slf4j接口-log4j实现组合,同时jdk的日志导入slf4j接口
我使用logback
1)
-主pom,所有依赖树枝叶反复mvn dependency:tree 去除slf4j-log4j12,log4j.jar本身由于历史原因可以保留(因为有历史代码直接调用log4j的api)
-添加logback-classic-1.2.3作为slf4j的实现,相当于jetty/tomcat 主服务包括其依赖common包内的 原slf4j api、原jdk jul api调用使用logback打印一个目录
-除了jul-to-slf4j.jar,jul -》 slf4j的重定向还需要:
SLF4JBridgeHandler.removeHandlersForRootLogger();
SLF4JBridgeHandler.install();
-classpath下留一个logback.xml,删除多余的,以免引起冲突,虽然一般war直接resources下的logback.xml优先;war依赖于scef,干掉war中的logback.xml,保留scef中的,因为scef要跑testcase也需要这个xml

2)自定义加载器(与jetty/tomcat类加载器平行),使用logback配合setContext打印另一个目录,记录一些自己的key 日志,因为依赖common包中有大量slf4j日志,我们需要自己的key日志
3)本地效果:

4)服务器效果
放到服务器上发现没有1)【common】的,只有2)【key】的,本来还想着在war/resources下面也放一个logback.xml,原来被启动脚本指定了启动参数外部logback.xml路径
cat localhost.log|grep logback


ps -ef|grep xxx也一样
点进去看里面的路径配置,找到文件grep

5)testcase 由于自定义加载器是AppCL的子CL,loadClass("org.slf4j.LoggerFactory");永远会返回主pom(appclassloader)的logback,主pom的slf4j与logback对其可见并被其修改context url,故都打印到2)的目录,具体看第10点
5.26改为,testcase日志都打到主pom对应的【common】文件夹中,而非2)【key】目录,类的相同通过对是否为同一个类加载器进行判断中的3.半
浙公网安备 33010602011771号