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
pull/4118/merge
Andy Wilkinson 9 years ago
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.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.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.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]) # IDENTITY ({sc-spring-boot}/context/ContextIdApplicationContextInitializer.{sc-ext}[ContextIdApplicationContextInitializer])
spring.application.name= spring.application.name=

@ -19,6 +19,7 @@ package org.springframework.boot.logging;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
@ -76,6 +77,13 @@ public class LoggingApplicationListener implements GenericApplicationListener {
*/ */
public static final String CONFIG_PROPERTY = "logging.config"; 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 * The name of the Spring property that contains the path where the logging
* configuration can be found. * configuration can be found.
@ -100,6 +108,8 @@ public class LoggingApplicationListener implements GenericApplicationListener {
private static MultiValueMap<LogLevel, String> LOG_LEVEL_LOGGERS; private static MultiValueMap<LogLevel, String> LOG_LEVEL_LOGGERS;
private static AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false);
static { static {
LOG_LEVEL_LOGGERS = new LinkedMultiValueMap<LogLevel, String>(); LOG_LEVEL_LOGGERS = new LinkedMultiValueMap<LogLevel, String>();
LOG_LEVEL_LOGGERS.add(LogLevel.DEBUG, "org.springframework.boot"); LOG_LEVEL_LOGGERS.add(LogLevel.DEBUG, "org.springframework.boot");
@ -201,6 +211,7 @@ public class LoggingApplicationListener implements GenericApplicationListener {
initializeEarlyLoggingLevel(environment); initializeEarlyLoggingLevel(environment);
initializeSystem(environment, this.loggingSystem); initializeSystem(environment, this.loggingSystem);
initializeFinalLoggingLevels(environment, this.loggingSystem); initializeFinalLoggingLevels(environment, this.loggingSystem);
registerShutdownHookIfNecessary(environment, this.loggingSystem);
} }
private String getExceptionConversionWord(ConfigurableEnvironment environment) { private String getExceptionConversionWord(ConfigurableEnvironment environment) {
@ -299,6 +310,19 @@ public class LoggingApplicationListener implements GenericApplicationListener {
return LogLevel.valueOf(level.toUpperCase()); 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) { public void setOrder(int order) {
this.order = order; this.order = order;
} }

@ -92,6 +92,17 @@ public abstract class LoggingSystem {
public void cleanUp() { 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. * Sets the logging level for a given logger.
* @param loggerName the name of the logger to set * @param loggerName the name of the logger to set

@ -40,6 +40,7 @@ import org.springframework.util.StringUtils;
* *
* @author Phillip Webb * @author Phillip Webb
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson
*/ */
public class JavaLoggingSystem extends AbstractLoggingSystem { public class JavaLoggingSystem extends AbstractLoggingSystem {
@ -114,4 +115,18 @@ public class JavaLoggingSystem extends AbstractLoggingSystem {
logger.setLevel(LEVELS.get(level)); 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)); 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(); getLoggerContext().updateLoggers();
} }
@Override
public Runnable getShutdownHandler() {
return new ShutdownHandler();
}
private LoggerConfig getRootLoggerConfig() { private LoggerConfig getRootLoggerConfig() {
return getLoggerContext().getConfiguration().getLoggerConfig(""); return getLoggerContext().getConfiguration().getLoggerConfig("");
} }
@ -209,4 +214,12 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem {
return (LoggerContext) LogManager.getContext(false); 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)); getLogger(loggerName).setLevel(LEVELS.get(level));
} }
@Override
public Runnable getShutdownHandler() {
return new ShutdownHandler();
}
private ch.qos.logback.classic.Logger getLogger(String name) { private ch.qos.logback.classic.Logger getLogger(String name) {
LoggerContext factory = getLoggerContext(); LoggerContext factory = getLoggerContext();
return factory return factory
@ -233,4 +238,13 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem {
return "unknown location"; 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.", "description": "Location of the log file.",
"sourceType": "org.springframework.boot.logging.LoggingApplicationListener" "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", "name": "spring.mandatory-file-encoding",
"sourceType": "org.springframework.boot.context.FileEncodingApplicationListener", "sourceType": "org.springframework.boot.context.FileEncodingApplicationListener",

@ -41,6 +41,7 @@ import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.support.GenericApplicationContext; import org.springframework.context.support.GenericApplicationContext;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -86,6 +87,8 @@ public class LoggingApplicationListenerTests {
@After @After
public void clear() { public void clear() {
LoggingSystem.get(getClass().getClassLoader()).cleanUp();
System.clearProperty(LoggingSystem.class.getName());
System.clearProperty("LOG_FILE"); System.clearProperty("LOG_FILE");
System.clearProperty("LOG_PATH"); System.clearProperty("LOG_PATH");
System.clearProperty("PID"); System.clearProperty("PID");
@ -93,7 +96,6 @@ public class LoggingApplicationListenerTests {
if (this.context != null) { if (this.context != null) {
this.context.close(); this.context.close();
} }
LoggingSystem.get(getClass().getClassLoader()).cleanUp();
} }
private String tmpDir() { private String tmpDir() {
@ -341,6 +343,30 @@ public class LoggingApplicationListenerTests {
this.logger.info("Hello world", new RuntimeException("Expected")); 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() { private boolean bridgeHandlerInstalled() {
Logger rootLogger = LogManager.getLogManager().getLogger(""); Logger rootLogger = LogManager.getLogManager().getLogger("");
Handler[] handlers = rootLogger.getHandlers(); Handler[] handlers = rootLogger.getHandlers();
@ -351,4 +377,41 @@ public class LoggingApplicationListenerTests {
} }
return false; 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…
Cancel
Save