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.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…
Reference in New Issue