Skip to content

Instantly share code, notes, and snippets.

@cykl
Created April 15, 2020 06:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cykl/c052a3f9c64d58a56e383319be2a8d52 to your computer and use it in GitHub Desktop.
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)
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