类加载器隔离朴实案例(二)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.半