The purpose of this issue is to remove reflection usage at runtime in a way that can be evaluated at build time in order to increase native image compatibility while keeping the size of the executable small.
In LoggingSystem
, logging systems are initialized as following:
private static final Map<String, String> SYSTEMS;
static {
Map<String, String> systems = new LinkedHashMap<>();
systems.put("ch.qos.logback.core.Appender", "org.springframework.boot.logging.logback.LogbackLoggingSystem");
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
"org.springframework.boot.logging.log4j2.Log4J2LoggingSystem");
systems.put("java.util.logging.LogManager", "org.springframework.boot.logging.java.JavaLoggingSystem");
SYSTEMS = Collections.unmodifiableMap(systems);
}
And then initialized when required by:
private static LoggingSystem get(ClassLoader classLoader, String loggingSystemClass) {
try {
Class<?> systemClass = ClassUtils.forName(loggingSystemClass, classLoader);
Constructor<?> constructor = systemClass.getDeclaredConstructor(ClassLoader.class);
constructor.setAccessible(true);
return (LoggingSystem) constructor.newInstance(classLoader);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
As experimented with @wilkinsona we can switch on lambdas:
static {
Map<String, Function<ClassLoader, LoggingSystem>> systems = new LinkedHashMap<>();
systems.put("ch.qos.logback.core.Appender", (classLoader) -> new LogbackLoggingSystem(classLoader));
systems.put("org.apache.logging.log4j.core.impl.Log4jContextFactory",
(classLoader) -> new Log4J2LoggingSystem(classLoader));
systems.put("java.util.logging.LogManager", (classLoader) -> new JavaLoggingSystem(classLoader));
SYSTEMS = Collections.unmodifiableMap(systems);
}
But multiple logging systems will likely be included in the native executable.
If we could assume that:
- ClassLoader
is used to load resources not select the right logging system
- Only one logging system is used in the application lifetime
Could we then implement something like:
private static final Function<ClassLoader, LoggingSystem> SYSTEM_FACTORY;
static {
ClassLoader classLoader = LoggingSystem.class.getClassLoader();
if (ClassUtils.isPresent("ch.qos.logback.core.Appender", classLoader)) {
SYSTEM_FACTORY = (cl) -> new LogbackLoggingSystem(cl);
}
else if (ClassUtils.isPresent("org.apache.logging.log4j.core.impl.Log4jContextFactory", classLoader)) {
SYSTEM_FACTORY = (cl) -> new Log4J2LoggingSystem(cl);
}
else {
SYSTEM_FACTORY = (cl) -> new JavaLoggingSystem(cl);
}
}
?
We could even include NoOpLoggingSystem
via the system property check.
Comment From: wilkinsona
We could even include NoOpLoggingSystem via the system property check.
I don't think we can. If you use LoggingSystem.SYSTEM_PROPERTY
to set the system property, LoggingSystem
is loaded and its static initialiser is invoked before the property is set.
Comment From: sdeleuze
@wilkinsona @philwebb Thanks a lot for this change.
Comment From: philwebb
@sdeleuze We might need to revert this one unfortunately due #23387
Comment From: sdeleuze
I am not sure reverting will solve anything, I will elaborate in the new issue created.