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.