Re: [D] Using Custom ClassLoader to Isolate Log42j and Slf4j . Console log: Properties contains an invalid element or attr ibute "property" [logging-log4j2]
zhanjinhao (via GitHub) <[email protected]>
| Newsgroups | gmane.comp.jakarta.log4j.devel |
|---|---|
| Message-ID | <ghd-D_kwDOAKJSSM4AiiR0-f3353863-d6e3-4aa9-a736-2a97e6055dc0__11328.7363130198$1761301997$gmane$org@gitbox.apache.org> |
GitHub user zhanjinhao added a comment to the discussion: Using Custom ClassLoader to Isolate Log42j and Slf4j. Console log: Properties contains an invalid element or attribute "property"
@vy thanks.
@jhl221123 I think I've found the real reason. Not about findResource()/findResources(). **It is about findLoadedClass().**
### Problem Analysis
I have compared my implementation with yours. And adjusting my implementation to yours step by step . I found if I don't introduce findLoadedClass() when overriding loadClass(), the directory will not be created correctly.
<img width="2189" height="818" alt="image" src="https://github.com/user-attachments/assets/25c9857a-9d23-4b59-bad5-aad5cf8edd90" />
If I don't extend URLClassLoader. And I still load class and resources in my way except including a check for already-loaded classes. the directory will be created correctly.
```
/**
* It will use commons-io package, so I package my project to a fat jar.
**/
package cn.addenda.loglearn;
import lombok.Getter;
import org.apache.commons.io.IOUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class LogClassLoader6 extends ClassLoader {
@Getter
private static LogClassLoader6 DEFAULT_LOADER;
private final List<String> logPrefixList = new ArrayList<>();
List<Jar> jars;
static {
ClassLoader.registerAsParallelCapable();
}
public LogClassLoader6(ClassLoader parent) {
super(parent);
jars = doGetJars();
logPrefixList.add("org.slf4j.");
logPrefixList.add("org.apache.logging.log4j.");
logPrefixList.add("org.apache.logging.slf4j.");
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass != null) {
return loadedClass;
}
boolean shouldIsolate = false;
for (String prefix : logPrefixList) {
if (name.startsWith(prefix)) {
shouldIsolate = true;
break;
}
}
if (shouldIsolate) {
loadedClass = doFindClass(name);
if (resolve) {
resolveClass(loadedClass);
}
return loadedClass;
}
return super.loadClass(name, resolve);
}
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
List<URL> allResources = new ArrayList<>();
for (Jar jar : jars) {
JarEntry jarEntry = jar.jarFile.getJarEntry(name);
if (jarEntry == null) {
continue;
}
try {
URL url = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name);
allResources.add(url);
} catch (Exception e) {
System.out.println(String.format("find file {} error", name));
e.printStackTrace();
}
}
Iterator<URL> iterator = allResources.iterator();
return new Enumeration<URL>() {
@Override
public boolean hasMoreElements() {
return iterator.hasNext();
}
@Override
public URL nextElement() {
return iterator.next();
}
};
}
@Override
public URL getResource(String name) {
// todo 设置为线程上下文类加载器,再从这里读取外部目录的log4j2.xml
for (Jar jar : jars) {
JarEntry jarEntry = jar.jarFile.getJarEntry(name);
if (jarEntry == null) {
continue;
}
try {
// 返回第一个
URL url = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + name);
return url;
} catch (Exception e) {
System.out.println(String.format("find file {} error", name));
e.printStackTrace();
}
}
return null;
}
public static synchronized void initDefaultLoader() {
if (DEFAULT_LOADER == null) {
// ClassLoader extensionClassLoader = ClassLoader.getSystemClassLoader().getParent();
ClassLoader extensionClassLoader = ClassLoader.getSystemClassLoader();
DEFAULT_LOADER = new LogClassLoader6(extensionClassLoader);
}
}
private Class<?> doFindClass(String name) throws ClassNotFoundException {
List<Jar> _allJarList = jars;
String concat = name.replace(".", "/").concat(".class");
for (Jar jar : _allJarList) {
JarEntry jarEntry = jar.jarFile.getJarEntry(concat);
if (jarEntry == null) {
continue;
}
try {
URL url = new URL("jar:file:" + jar.sourceFile.getAbsolutePath() + "!/" + concat);
byte[] byteArray = IOUtils.toByteArray(url);
return defineClass(name, byteArray, 0, byteArray.length);
} catch (Exception e) {
System.out.println(String.format("find class %s error", name));
}
}
throw new ClassNotFoundException("can not find " + name);
}
private List<Jar> doGetJars() {
List<Jar> _allJarList = new ArrayList<>();
File agentJarDir = AgentPackagePath.getPath();
System.out.println("Agent base directory: " + agentJarDir);
File logLibDir = new File(new File(agentJarDir, "lib"), "log");
if (logLibDir.exists() && logLibDir.isDirectory()) {
String[] list = logLibDir.list((dir, name) -> name.endsWith(".jar"));
if (list == null || list.length == 0) {
return _allJarList;
}
for (String s : list) {
File jarSourceFile = new File(logLibDir, s);
try {
Jar jar = new Jar(new JarFile(jarSourceFile), jarSourceFile);
_allJarList.add(jar);
System.out.println(String.format("load jar %s success.", jarSourceFile));
} catch (Exception e) {
System.out.println(String.format("jar %s load fail.", jarSourceFile));
e.printStackTrace();
}
}
}
return _allJarList;
}
private static class Jar {
/**
* jar文件对对应的jarFile对象
*/
private final JarFile jarFile;
/**
* jar文件
*/
private final File sourceFile;
public Jar(JarFile jarFile, File sourceFile) {
this.jarFile = jarFile;
this.sourceFile = sourceFile;
}
}
}
```
In my production project, I will extent URLClassLoader because I am indeed not clear about operating jar.
### Different Class
With my previous LogClassLoader, the codebelow will output : loggerContextClass != loggerContextClass2.
```
loggerFactoryClass = Class.forName("org.slf4j.LoggerFactory", true, defaultLoader);
loggerFactoryClass2 = Class.forName("org.slf4j.LoggerFactory", true, defaultLoader);
if (loggerContextClass == loggerFactoryClass2) {
System.out.println("loggerContextClass == loggerContextClass2");
} else {
System.out.println("loggerContextClass != loggerContextClass2");
}
```
**A class‘s binary is loaded from the jar multiple times, each time the class is different.**
### Conclusion
So I think the real reason is that I don't check the already-loaded classes
However, as for how it affects log4j2, it's too difficult for me to analyze. Log4j2 loaded too many classes at startup.
GitHub link: https://github.com/apache/logging-log4j2/discussions/3960#discussioncomment-14771588
----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: [email protected]