Created
April 15, 2020 06:49
-
-
Save cykl/c052a3f9c64d58a56e383319be2a8d52 to your computer and use it in GitHub Desktop.
How to replace Spring boot's default logback config from a library? (see https://stackoverflow.com/questions/48539217/how-to-replace-spring-boots-default-logback-config-from-a-library)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.springframework.boot.SpringApplication; | |
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; | |
import org.springframework.boot.logging.LoggingApplicationListener; | |
import org.springframework.boot.logging.LoggingSystem; | |
import org.springframework.boot.logging.logback.LogbackLoggingSystem; | |
import org.springframework.context.ApplicationContext; | |
import org.springframework.context.ApplicationEvent; | |
import org.springframework.context.event.GenericApplicationListener; | |
import org.springframework.core.ResolvableType; | |
import org.springframework.core.env.ConfigurableEnvironment; | |
import org.springframework.core.env.MapPropertySource; | |
import org.springframework.core.env.MutablePropertySources; | |
import org.springframework.util.ReflectionUtils; | |
import org.springframework.util.StringUtils; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.Arrays; | |
import java.util.Collections; | |
import static org.springframework.boot.logging.LoggingApplicationListener.CONFIG_PROPERTY; | |
/** | |
* Load {@literal xxx-slog-logback.xml} using {@literal logging.config} if {@literal logging.config} is not set and no | |
* logback configuration file is found. | |
* <p> | |
* Unfortunately, Spring does not allow a "starter" to ship a logback configuration file that would be loaded by default | |
* if none is provided by the application. By running this listener just before {@link LoggingApplicationListener}, we | |
* are able to inspect the logback configuration and use {@literal logging.config} to pass our own if none is detected. | |
* <p> | |
* This is hacky, but does the job. Getting ride of this listener would mean that we cannot ship our default | |
* configuration with the starter and require from our user to create a {@literal logback.xml} file (which would include | |
* our configuration). Doable but not a great developer experience. | |
*/ | |
public class SLogApplicationListener implements GenericApplicationListener { | |
private static final Class<?>[] EVENT_TYPES = {ApplicationEnvironmentPreparedEvent.class}; | |
private static final Class<?>[] SOURCE_TYPES = {SpringApplication.class, ApplicationContext.class}; | |
@Override | |
public int getOrder() { | |
return LoggingApplicationListener.DEFAULT_ORDER - 1; | |
} | |
@Override | |
public boolean supportsEventType(ResolvableType eventType) { | |
return isAssignableFrom(eventType.getRawClass(), EVENT_TYPES); | |
} | |
@Override | |
public boolean supportsSourceType(Class<?> sourceType) { | |
return isAssignableFrom(sourceType, SOURCE_TYPES); | |
} | |
private boolean isAssignableFrom(Class<?> type, Class<?>... supportedTypes) { | |
return Arrays.stream(supportedTypes).anyMatch(supportedType -> supportedType.isAssignableFrom(type)); | |
} | |
@Override | |
public void onApplicationEvent(ApplicationEvent event) { | |
if (event instanceof ApplicationEnvironmentPreparedEvent) { | |
ApplicationEnvironmentPreparedEvent aepEvent = (ApplicationEnvironmentPreparedEvent) event; | |
overrideLogbackConfig(aepEvent); | |
} | |
} | |
private void overrideLogbackConfig(ApplicationEnvironmentPreparedEvent event) { | |
ConfigurableEnvironment env = event.getEnvironment(); | |
String logConfig = env.getProperty(CONFIG_PROPERTY); | |
if (StringUtils.hasLength(logConfig)) { | |
return; // Never override user supplied logging.config | |
} | |
// Must be super careful to not initialize the LoggingSystem (ctor does nothing) | |
ClassLoader classLoader = event.getSpringApplication().getClassLoader(); | |
LogbackLoggingSystem logbackLoggingSystem = getLogbackLoggingSystem(classLoader); | |
if (logbackLoggingSystem == null) { | |
return; // Logback is not active, don't touch anything | |
} | |
if (isLogbackConfPresent(logbackLoggingSystem)) { | |
return; // A config file at well known location has been found, don't touch anything | |
} | |
setConfig(env.getPropertySources()); | |
} | |
private LogbackLoggingSystem getLogbackLoggingSystem(ClassLoader classLoader) { | |
LoggingSystem loggingSystem = LoggingSystem.get(classLoader); | |
if (loggingSystem instanceof LogbackLoggingSystem) { | |
return (LogbackLoggingSystem) loggingSystem; | |
} | |
return null; | |
} | |
private boolean isLogbackConfPresent(LogbackLoggingSystem loggingSystem) { | |
Method method = ReflectionUtils.findMethod(LogbackLoggingSystem.class, "getSpringInitializationConfig"); | |
method.setAccessible(true); | |
try { | |
String ret = (String) method.invoke(loggingSystem); | |
return ret != null; | |
} catch (IllegalAccessException | InvocationTargetException e) { | |
return false; | |
} | |
} | |
private void setConfig(MutablePropertySources sources) { | |
MapPropertySource mapPropertySource = new MapPropertySource( | |
"xxx-slog-logging-config", | |
Collections.singletonMap(CONFIG_PROPERTY, "classpath:xxx-slog-logback.xml")); | |
sources.addLast(mapPropertySource); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment