类加载器隔离朴实案例(三)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);
}
}
浙公网安备 33010602011771号