Add opt-in support for registering a shutdown hook to shut down logging
This commit adds a new property, logging.register-shutdown-hook, that when set to true, will cause LoggingApplicationListener to register a shutdown hook the first time it initializes a logging system. When the JVM exits, the shutdown hook shuts down each of the supported logging systems, ensuring that all of their appenders have been flushed and closed. Closes gh-4026
This commit is contained in:
parent
4594edf4c4
commit
36d4246289
|
|
@ -65,6 +65,7 @@ content into your application; rather pick only the properties that you need.
|
|||
logging.pattern.console= # appender pattern for output to the console (only supported with the default logback setup)
|
||||
logging.pattern.file= # appender pattern for output to the file (only supported with the default logback setup)
|
||||
logging.pattern.level= # appender pattern for the log level (default %5p, only supported with the default logback setup)
|
||||
logging.register-shutdown-hook=false # register a shutdown hook for the logging system when it is initialized
|
||||
|
||||
# IDENTITY ({sc-spring-boot}/context/ContextIdApplicationContextInitializer.{sc-ext}[ContextIdApplicationContextInitializer])
|
||||
spring.application.name=
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package org.springframework.boot.logging;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
|
@ -76,6 +77,13 @@ public class LoggingApplicationListener implements GenericApplicationListener {
|
|||
*/
|
||||
public static final String CONFIG_PROPERTY = "logging.config";
|
||||
|
||||
/**
|
||||
* The name of the Spring property that controls the registration of a shutdown hook
|
||||
* to shut down the logging system when the JVM exits.
|
||||
* @see LoggingSystem#getShutdownHandler
|
||||
*/
|
||||
public static final String REGISTER_SHOW_HOOK_PROPERTY = "logging.register-shutdown-hook";
|
||||
|
||||
/**
|
||||
* The name of the Spring property that contains the path where the logging
|
||||
* configuration can be found.
|
||||
|
|
@ -100,6 +108,8 @@ public class LoggingApplicationListener implements GenericApplicationListener {
|
|||
|
||||
private static MultiValueMap<LogLevel, String> LOG_LEVEL_LOGGERS;
|
||||
|
||||
private static AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false);
|
||||
|
||||
static {
|
||||
LOG_LEVEL_LOGGERS = new LinkedMultiValueMap<LogLevel, String>();
|
||||
LOG_LEVEL_LOGGERS.add(LogLevel.DEBUG, "org.springframework.boot");
|
||||
|
|
@ -201,6 +211,7 @@ public class LoggingApplicationListener implements GenericApplicationListener {
|
|||
initializeEarlyLoggingLevel(environment);
|
||||
initializeSystem(environment, this.loggingSystem);
|
||||
initializeFinalLoggingLevels(environment, this.loggingSystem);
|
||||
registerShutdownHookIfNecessary(environment, this.loggingSystem);
|
||||
}
|
||||
|
||||
private String getExceptionConversionWord(ConfigurableEnvironment environment) {
|
||||
|
|
@ -299,6 +310,19 @@ public class LoggingApplicationListener implements GenericApplicationListener {
|
|||
return LogLevel.valueOf(level.toUpperCase());
|
||||
}
|
||||
|
||||
private void registerShutdownHookIfNecessary(Environment environment,
|
||||
LoggingSystem loggingSystem) {
|
||||
boolean registerShutdownHook = new RelaxedPropertyResolver(environment)
|
||||
.getProperty(REGISTER_SHOW_HOOK_PROPERTY, Boolean.class, false);
|
||||
if (registerShutdownHook) {
|
||||
Runnable shutdownHandler = loggingSystem.getShutdownHandler();
|
||||
if (shutdownHandler != null
|
||||
&& shutdownHookRegistered.compareAndSet(false, true)) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,17 @@ public abstract class LoggingSystem {
|
|||
public void cleanUp() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Runnable} that can handle shutdown of this logging system when the
|
||||
* JVM exits. The default implementation returns {@code null}, indicating that no
|
||||
* shutdown is required.
|
||||
*
|
||||
* @return the shutdown handler, or {@code null}
|
||||
*/
|
||||
public Runnable getShutdownHandler() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the logging level for a given logger.
|
||||
* @param loggerName the name of the logger to set
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import org.springframework.util.StringUtils;
|
|||
*
|
||||
* @author Phillip Webb
|
||||
* @author Dave Syer
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaLoggingSystem extends AbstractLoggingSystem {
|
||||
|
||||
|
|
@ -114,4 +115,18 @@ public class JavaLoggingSystem extends AbstractLoggingSystem {
|
|||
logger.setLevel(LEVELS.get(level));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getShutdownHandler() {
|
||||
return new ShutdownHandler();
|
||||
}
|
||||
|
||||
private final class ShutdownHandler implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LogManager.getLogManager().reset();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,4 +116,18 @@ public class Log4JLoggingSystem extends Slf4JLoggingSystem {
|
|||
logger.setLevel(LEVELS.get(level));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getShutdownHandler() {
|
||||
return new ShutdownHandler();
|
||||
}
|
||||
|
||||
private static final class ShutdownHandler implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LogManager.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,6 +197,11 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem {
|
|||
getLoggerContext().updateLoggers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getShutdownHandler() {
|
||||
return new ShutdownHandler();
|
||||
}
|
||||
|
||||
private LoggerConfig getRootLoggerConfig() {
|
||||
return getLoggerContext().getConfiguration().getLoggerConfig("");
|
||||
}
|
||||
|
|
@ -209,4 +214,12 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem {
|
|||
return (LoggerContext) LogManager.getContext(false);
|
||||
}
|
||||
|
||||
private final class ShutdownHandler implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
getLoggerContext().stop();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,6 +199,11 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem {
|
|||
getLogger(loggerName).setLevel(LEVELS.get(level));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getShutdownHandler() {
|
||||
return new ShutdownHandler();
|
||||
}
|
||||
|
||||
private ch.qos.logback.classic.Logger getLogger(String name) {
|
||||
LoggerContext factory = getLoggerContext();
|
||||
return factory
|
||||
|
|
@ -233,4 +238,13 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem {
|
|||
return "unknown location";
|
||||
}
|
||||
|
||||
private final class ShutdownHandler implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
getLoggerContext().stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,12 @@
|
|||
"description": "Location of the log file.",
|
||||
"sourceType": "org.springframework.boot.logging.LoggingApplicationListener"
|
||||
},
|
||||
{
|
||||
"name": "logging.register-shutdown-hook",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Register a shutdown hook for the logging system when it is initialized.",
|
||||
"sourceType": "org.springframework.boot.logging.LoggingApplicationListener"
|
||||
},
|
||||
{
|
||||
"name": "spring.mandatory-file-encoding",
|
||||
"sourceType": "org.springframework.boot.context.FileEncodingApplicationListener",
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import org.springframework.context.event.ContextClosedEvent;
|
|||
import org.springframework.context.support.GenericApplicationContext;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
|
@ -86,6 +87,8 @@ public class LoggingApplicationListenerTests {
|
|||
|
||||
@After
|
||||
public void clear() {
|
||||
LoggingSystem.get(getClass().getClassLoader()).cleanUp();
|
||||
System.clearProperty(LoggingSystem.class.getName());
|
||||
System.clearProperty("LOG_FILE");
|
||||
System.clearProperty("LOG_PATH");
|
||||
System.clearProperty("PID");
|
||||
|
|
@ -93,7 +96,6 @@ public class LoggingApplicationListenerTests {
|
|||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
LoggingSystem.get(getClass().getClassLoader()).cleanUp();
|
||||
}
|
||||
|
||||
private String tmpDir() {
|
||||
|
|
@ -341,6 +343,30 @@ public class LoggingApplicationListenerTests {
|
|||
this.logger.info("Hello world", new RuntimeException("Expected"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shutdownHookIsNotRegisteredByDefault() throws Exception {
|
||||
System.setProperty(LoggingSystem.class.getName(),
|
||||
NullShutdownHandlerLoggingSystem.class.getName());
|
||||
this.initializer.onApplicationEvent(
|
||||
new ApplicationStartedEvent(new SpringApplication(), NO_ARGS));
|
||||
this.initializer.initialize(this.context.getEnvironment(),
|
||||
this.context.getClassLoader());
|
||||
assertThat(NullShutdownHandlerLoggingSystem.shutdownHandlerRequested, is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shutdownHookCanBeRegistered() throws Exception {
|
||||
System.setProperty(LoggingSystem.class.getName(),
|
||||
NullShutdownHandlerLoggingSystem.class.getName());
|
||||
EnvironmentTestUtils.addEnvironment(this.context,
|
||||
"logging.register_shutdown_hook:true");
|
||||
this.initializer.onApplicationEvent(
|
||||
new ApplicationStartedEvent(new SpringApplication(), NO_ARGS));
|
||||
this.initializer.initialize(this.context.getEnvironment(),
|
||||
this.context.getClassLoader());
|
||||
assertThat(NullShutdownHandlerLoggingSystem.shutdownHandlerRequested, is(true));
|
||||
}
|
||||
|
||||
private boolean bridgeHandlerInstalled() {
|
||||
Logger rootLogger = LogManager.getLogManager().getLogger("");
|
||||
Handler[] handlers = rootLogger.getHandlers();
|
||||
|
|
@ -351,4 +377,41 @@ public class LoggingApplicationListenerTests {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class NullShutdownHandlerLoggingSystem extends AbstractLoggingSystem {
|
||||
|
||||
static boolean shutdownHandlerRequested = false;
|
||||
|
||||
public NullShutdownHandlerLoggingSystem(ClassLoader classLoader) {
|
||||
super(classLoader);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getStandardConfigLocations() {
|
||||
return new String[] { "foo.bar" };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadDefaults(LoggingInitializationContext initializationContext,
|
||||
LogFile logFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadConfiguration(
|
||||
LoggingInitializationContext initializationContext, String location,
|
||||
LogFile logFile) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLogLevel(String loggerName, LogLevel level) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Runnable getShutdownHandler() {
|
||||
shutdownHandlerRequested = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue