类加载器隔离朴实案例(三)logback实战加密
背景:
公司框架日志包经常冲突太乱了,经常打不出来,拟搞一套私有的日志
框架 tomcat appclassloader
经过实践,本方法不适用log4j2 高版本slf4j 高版本logback
基于
slf4j-api-1.7.25.jar
logback-classic-1.2.3.jar
logback-core-1.2.3.jar
0:
类加载器加载内部资源
字节码加密解密(应对扫描)
适配tomcat容器
代码仓库:
/Users/joyce/work/MyTest/MyMock/src/main/java/com/test/privatelogclass/PrivateLogFactory.java
实践
1 没有-D,也没有你config
start to find resource file logback.groovy
start to find resource file logback.xml (这在log4j2和高版本logback中没有)
日志打到log
可以看到虽然重定向了logback.xml但是仍然getresource了logback.xml
而且日志打到重定向的logP
2 -Dlogback.configurationFile=file:///Users/joyce/work/MyTest/MyMock/src/main/resources/privatelog/privatelogD.xml
没有findresouce logback.xml
而且日志打到-D的logD
3 config
没有findresouce logback.xml
而且日志打到-D的logP
package com.test.privatelogclass; import java.io.*; import java.lang.reflect.Method; /** * Created by joyce on 2020/3/28. */ public class PrivateLogFactory { public static void main(String []f) throws Exception { InputStream inputStream = PrivateLogFactory.class.getClassLoader().getResourceAsStream("privatelog/slf4j-api-1.7.25.jar"); byte [] bytes = toByteArray(inputStream); File ff = new File("/Users/joyce/work/MyTest/MyMock/src/main/resources/privatelog/api"); FileOutputStream fop = new FileOutputStream(ff); fop.write(encode(bytes)); fop.close(); } public static byte[] toByteArray(InputStream input) throws IOException { ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buffer = new byte[1024*4]; int n = 0; while (-1 != (n = input.read(buffer))) { output.write(buffer, 0, n); } return output.toByteArray(); } public static byte [] encode(byte [] source) throws Exception { byte[] out = new byte[source.length]; for (int i = 0; i < source.length; ++i) { out[i] = source[source.length - 1 - i]; } return out; } public static PrivateLogger getLogger(Class c) { try { Class cl = PrivateLogClassLoader.getPrivateLogClassLoader().loadClass("org.slf4j.LoggerFactory"); Method method = cl.getMethod("getLogger", Class.class); Object log = method.invoke(null, c); PrivateLogger privateLogger = new PrivateLogger(); privateLogger.setLogger(log); return privateLogger; } catch (Exception e) { throw new RuntimeException(e); } } public static class PrivateLogger { public void setLogger(Object logger) { this.logger = logger; } Object logger; public void error(String var1, Throwable var2) { try { Class logcl = PrivateLogClassLoader.getPrivateLogClassLoader().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) ; } } public void error(Throwable var1) { try { Class logcl = PrivateLogClassLoader.getPrivateLogClassLoader().loadClass("org.slf4j.Logger"); Method info = logcl.getMethod("error", String.class, Throwable.class); info.invoke(this.logger, var1.getMessage(), var1); } catch (Exception e) { throw new RuntimeException(e) ; } } public void info(String var1, Object... var2) { try { Class logcl = PrivateLogClassLoader.getPrivateLogClassLoader().loadClass("org.slf4j.Logger"); Method info = logcl.getMethod("info", String.class, Object[].class); info.invoke(this.logger, var1, var2); } catch (Exception e) { throw new RuntimeException(e) ; } } } }
package com.test.privatelogclass; /** * Created by joyce on 2022/5/19. */ import java.io.*; import java.lang.reflect.Method; import java.net.URL; import java.util.HashMap; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; public class PrivateLogClassLoader extends ClassLoader { JarInputStream[] list = null; private HashMap<String, byte[]> classes = new HashMap<>(); private static volatile PrivateLogClassLoader privateLogClassLoader; private static boolean config = true; private static final String [] JAR_URLS = {"api", "classic", "core"}; static { try { JarInputStream [] inputStreams = new JarInputStream[JAR_URLS.length]; for(int i=0; i<JAR_URLS.length; ++i) { InputStream inputStream = PrivateLogClassLoader.class.getClassLoader().getResourceAsStream("privatelog/"+JAR_URLS[i]); byte [] bytes = PrivateLogFactory.toByteArray(inputStream); inputStreams[i] = new JarInputStream(new ByteArrayInputStream(PrivateLogFactory.encode(bytes))); } privateLogClassLoader = new PrivateLogClassLoader(inputStreams); privateLogClassLoader.configureLogback(); } catch (Exception e) { printError(e); } } public static PrivateLogClassLoader getPrivateLogClassLoader() { return privateLogClassLoader; } private PrivateLogClassLoader(JarInputStream [] jarInputStream) { this.list = jarInputStream; for(JarInputStream jar : list) { JarEntry entry; try { while ((entry = jar.getNextJarEntry()) != null) { String name = entry.getName(); ByteArrayOutputStream out = new ByteArrayOutputStream(); int len = -1; byte [] tmp = new byte[1024]; while ((len = jar.read(tmp)) != -1) { out.write(tmp, 0, len); } byte[] bytes = out.toByteArray(); classes.put(name, bytes); } } catch (Exception e) { printError(e); } } printInfo("total classes - " + classes.size()); } private void configureLogback() throws Exception { if(!config) return; Boolean useLogbackxmlInAppClassLoader = false; if(useLogbackxmlInAppClassLoader) return; Class clLoggerFactory = loadClass("org.slf4j.LoggerFactory"); printInfo(clLoggerFactory.getClassLoader()); printInfo(clLoggerFactory.getClass().getClassLoader()); printInfo(this.getClass()); printInfo(this.getClass().getClassLoader()); printInfo(this.getClass().getClass().getClassLoader()); // 有可能在复杂环境中,pom中也有slf4j,直接由系统类加载器加载,那么就不重置环境了 if(clLoggerFactory.getClassLoader() != this) return; 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("privatelog/privatelogP.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); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { printInfo("start to find class " + name); try { InputStream in = getResourceAsStream(name.replace('.', '/') + ".class"); ByteArrayOutputStream out = new ByteArrayOutputStream(); int len = -1; byte[] tmp = new byte[1024]; while ((len = in.read(tmp)) != -1) { out.write(tmp, 0, len); } byte[] bytes = out.toByteArray(); return defineClass(name, bytes, 0, bytes.length); } catch (Exception e) { printError(e); } return super.findClass(name); } @Override public InputStream getResourceAsStream(String name) { printInfo("start to find resource stream " + name); if(classes.containsKey(name)) { byte [] res = classes.get(name); classes.remove(name); return new ByteArrayInputStream(res); } printInfo("getResourceAsStream - error - " + name); return super.getResourceAsStream(name); } @Override public URL getResource(String name) { printInfo("start to find resource file " + name); if(name.equals("logback.xml")) { return this.getClass().getClassLoader().getResource("privatelog/privatelog.xml"); } return null; } private static void printInfo(String msg) { System.out.println(msg); } private static void printInfo(Object msg) { System.out.println(msg); } private static void printError(Exception e) { e.printStackTrace(); } //-Dlogback.configurationFile=file:///Users/joyce/work/MyTest/MyMock/src/main/resources/privatelog/privatelogD.xml public static void main(String []f) { PrivateLogFactory.PrivateLogger logger = PrivateLogFactory.getLogger(PrivateLogFactory.class); logger.error("eeexxx", new Exception("ex22")); logger.error(new Exception("ex33333")); logger.info("infoinfo"); logger.info("infoinfo {}", 11); logger.info("infoinfo {} {}", 21, 31); } }