From cc4e845ae112d8af765f71ebb0eb51d75f20c1d0 Mon Sep 17 00:00:00 2001 From: Vedran Pavic Date: Tue, 5 Jul 2016 11:29:35 +0200 Subject: [PATCH] Use `SizeAndTimeBasedRollingPolicy` for default file appender Previously, `FixedWindowRollingPolicy` with size limit of 10 MB was used as default file appender. This commit changes the default file appender to use `SizeAndTimeBasedRollingPolicy` and introduces two new properties to improve log file configuration capabilities - `logging.file.max-history` to limit the number of archive log files to keep and `logging.file.max-size` to limit the log file size. --- .../appendix-application-properties.adoc | 2 + .../main/asciidoc/spring-boot-features.adoc | 17 ++++- .../boot/logging/LoggingSystemProperties.java | 13 ++++ .../logback/DefaultLogbackConfiguration.java | 39 ++++++------ ...itional-spring-configuration-metadata.json | 14 +++++ .../boot/logging/logback/file-appender.xml | 10 ++- .../logback/LogbackLoggingSystemTests.java | 62 +++++++++++++++++-- 7 files changed, 126 insertions(+), 31 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 84cbb690d5c0..1672ee932730 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -41,6 +41,8 @@ content into your application; rather pick only the properties that you need. logging.config= # Location of the logging configuration file. For instance `classpath:logback.xml` for Logback logging.exception-conversion-word=%wEx # Conversion word used when logging exceptions. logging.file= # Log file name. For instance `myapp.log` + logging.file.max-history= # Maximum of archive log files to keep. Only supported with the default logback setup. + logging.file.max-size= # Maximum log file size. Only supported with the default logback setup. logging.level.*= # Log levels severity mapping. For instance `logging.level.org.springframework=DEBUG` logging.path= # Location of the log file. For instance `/var/log` logging.pattern.console= # Appender pattern for output to the console. Only supported with the default logback setup. diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 74010a8c33ed..f1a8286da204 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1496,8 +1496,13 @@ current directory. relative to the current directory. |=== -Log files will rotate when they reach 10 MB and as with console output, `ERROR`, `WARN` -and `INFO` level messages are logged by default. +Log files will rotate daily and, in addition to time limit, when they reach the size limit +which is 10 MB by default. Size limit can be changed using `logging.file.max-size` +property. By default, archived log file is unbounded meaning entire history will be +preserved. You can use `logging.file.max-history` property to limit the number of archive +files to keep. + +As with console output, `ERROR`, `WARN` and `INFO` level messages are logged by default. NOTE: The logging system is initialized early in the application lifecycle and as such logging properties will not be found in property files loaded via `@PropertySource` @@ -1584,6 +1589,14 @@ To help with the customization some other properties are transferred from the Sp |`LOG_FILE` |Used in default log configuration if defined. +|`logging.file.max-size` +|`LOG_FILE_MAX_SIZE` +|Maximum log file size (if LOG_FILE enabled). (Only supported with the default logback setup.) + +|`logging.file.max-history` +|`LOG_FILE_MAX_HISTORY` +|Maximum number of archive log files to keep (if LOG_FILE enabled). (Only supported with the default logback setup.) + |`logging.path` |`LOG_PATH` |Used in default log configuration if defined. diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java index 5794911ff9cb..08161d05b416 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java @@ -29,6 +29,7 @@ * @author Andy Wilkinson * @author Phillip Webb * @author Madhura Bhave + * @author Vedran Pavic * @since 2.0.0 */ public class LoggingSystemProperties { @@ -63,6 +64,16 @@ public class LoggingSystemProperties { */ public static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN"; + /** + * The name of the System property that contains the file log max history. + */ + public static final String FILE_MAX_HISTORY = "LOG_FILE_MAX_HISTORY"; + + /** + * The name of the System property that contains the file log max size. + */ + public static final String FILE_MAX_SIZE = "LOG_FILE_MAX_SIZE"; + /** * The name of the System property that contains the log level pattern. */ @@ -89,6 +100,8 @@ public void apply(LogFile logFile) { "exception-conversion-word"); setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "pattern.console"); setSystemProperty(resolver, FILE_LOG_PATTERN, "pattern.file"); + setSystemProperty(resolver, FILE_MAX_HISTORY, "file.max-history"); + setSystemProperty(resolver, FILE_MAX_SIZE, "file.max-size"); setSystemProperty(resolver, LOG_LEVEL_PATTERN, "pattern.level"); setSystemProperty(PID_KEY, new ApplicationPid().toString()); if (logFile != null) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java index 17b92c6f24ce..c41d69b986f7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java @@ -24,9 +24,9 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; import ch.qos.logback.core.ConsoleAppender; -import ch.qos.logback.core.rolling.FixedWindowRollingPolicy; +import ch.qos.logback.core.CoreConstants; import ch.qos.logback.core.rolling.RollingFileAppender; -import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy; +import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; import ch.qos.logback.core.util.FileSize; import ch.qos.logback.core.util.OptionHelper; @@ -45,6 +45,7 @@ * * @author Phillip Webb * @author Madhura Bhave + * @author Vedran Pavic * @since 1.1.2 */ class DefaultLogbackConfiguration { @@ -57,6 +58,8 @@ class DefaultLogbackConfiguration { private static final String FILE_LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} " + "${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"; + private static final String MAX_FILE_SIZE = "10MB"; + private static final Charset UTF8 = Charset.forName("UTF-8"); private final PropertyResolver patterns; @@ -140,34 +143,32 @@ private Appender fileAppender(LogbackConfigurator config, config.start(encoder); appender.setFile(logFile); setRollingPolicy(appender, config, logFile); - setMaxFileSize(appender, config); config.appender("FILE", appender); return appender; } private void setRollingPolicy(RollingFileAppender appender, LogbackConfigurator config, String logFile) { - FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy(); - rollingPolicy.setFileNamePattern(logFile + ".%i"); - appender.setRollingPolicy(rollingPolicy); - rollingPolicy.setParent(appender); - config.start(rollingPolicy); - } - - private void setMaxFileSize(RollingFileAppender appender, - LogbackConfigurator config) { - SizeBasedTriggeringPolicy triggeringPolicy = new SizeBasedTriggeringPolicy<>(); + SizeAndTimeBasedRollingPolicy rollingPolicy = + new SizeAndTimeBasedRollingPolicy<>(); + rollingPolicy.setFileNamePattern(logFile + ".%d{yyyy-MM-dd}.%i.gz"); + String maxFileSize = this.patterns.getProperty("logging.file.max-size", + MAX_FILE_SIZE); try { - triggeringPolicy.setMaxFileSize(FileSize.valueOf("10MB")); + rollingPolicy.setMaxFileSize(FileSize.valueOf(maxFileSize)); } catch (NoSuchMethodError ex) { // Logback < 1.1.8 used String configuration - Method method = ReflectionUtils.findMethod(SizeBasedTriggeringPolicy.class, - "setMaxFileSize", String.class); - ReflectionUtils.invokeMethod(method, triggeringPolicy, "10MB"); + Method method = ReflectionUtils.findMethod( + SizeAndTimeBasedRollingPolicy.class, "setMaxFileSize", String.class); + ReflectionUtils.invokeMethod(method, rollingPolicy, maxFileSize); } - appender.setTriggeringPolicy(triggeringPolicy); - config.start(triggeringPolicy); + int maxHistory = this.patterns.getProperty("logging.file.max-history", + Integer.class, CoreConstants.UNBOUND_HISTORY); + rollingPolicy.setMaxHistory(maxHistory); + appender.setRollingPolicy(rollingPolicy); + rollingPolicy.setParent(appender); + config.start(rollingPolicy); } } diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 04dabaef52d8..48905f178d1a 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -69,6 +69,20 @@ "description": "Name of the log file. Names can be an exact location or relative to the current directory.", "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener" }, + { + "name": "logging.file.max-size", + "type": "java.lang.String", + "description": "Maximum log file size. Only supported with the default logback setup.", + "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener", + "defaultValue": "10MB" + }, + { + "name": "logging.file.max-history", + "type": "java.lang.Integer", + "description": "Maximum number of archive log files to keep. Only supported with the default logback setup.", + "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener", + "defaultValue": 0 + }, { "name": "logging.level", "type": "java.util.Map", diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml index 034cfa562d14..383e02750a09 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml @@ -12,12 +12,10 @@ initialization performed by Boot ${FILE_LOG_PATTERN} ${LOG_FILE} - - ${LOG_FILE}.%i + + ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz + ${MAX_FILE_SIZE:-10MB} + ${MAX_HISTORY:-0} - - 10MB - diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index 2c5b047bd672..046332edef6b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -27,6 +27,10 @@ import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.spi.LoggerContextListener; +import ch.qos.logback.core.ConsoleAppender; +import ch.qos.logback.core.CoreConstants; +import ch.qos.logback.core.rolling.RollingFileAppender; +import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; import org.apache.commons.logging.Log; import org.apache.commons.logging.impl.SLF4JLogFactory; import org.hamcrest.Matcher; @@ -48,6 +52,7 @@ import org.springframework.boot.testsupport.assertj.Matched; import org.springframework.boot.testsupport.rule.OutputCapture; import org.springframework.mock.env.MockEnvironment; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.FileCopyUtils; import org.springframework.util.StringUtils; @@ -66,6 +71,7 @@ * @author Andy Wilkinson * @author Ben Hale * @author Madhura Bhave + * @author Vedran Pavic */ public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { @@ -120,15 +126,16 @@ public void withFile() throws Exception { assertThat(getLineWithText(output, "Hello world")).contains("INFO"); assertThat(file.exists()).isTrue(); assertThat(getLineWithText(file, "Hello world")).contains("INFO"); + assertThat(ReflectionTestUtils.getField(getRollingPolicy(), "maxFileSize") + .toString()).isEqualTo("10 MB"); + assertThat(getRollingPolicy().getMaxHistory()).isEqualTo( + CoreConstants.UNBOUND_HISTORY); } @Test public void testBasicConfigLocation() throws Exception { this.loggingSystem.beforeInitialize(); - ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); - LoggerContext context = (LoggerContext) factory; - Logger root = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); - assertThat(root.getAppender("CONSOLE")).isNotNull(); + assertThat(getConsoleAppender()).isNotNull(); } @Test @@ -339,6 +346,35 @@ public void testFilePatternProperty() throws Exception { assertThat(getLineWithText(file, "Hello world")).doesNotContain("INFO"); } + @Test + public void testMaxFileSizeProperty() throws Exception { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("logging.file.max-size", "100MB"); + LoggingInitializationContext loggingInitializationContext = + new LoggingInitializationContext(environment); + File file = new File(tmpDir(), "logback-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + this.loggingSystem.initialize(loggingInitializationContext, null, logFile); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")).contains("INFO"); + assertThat(ReflectionTestUtils.getField(getRollingPolicy(), "maxFileSize") + .toString()).isEqualTo("100 MB"); + } + + @Test + public void testMaxHistoryProperty() throws Exception { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("logging.file.max-history", "30"); + LoggingInitializationContext loggingInitializationContext = + new LoggingInitializationContext(environment); + File file = new File(tmpDir(), "logback-test.log"); + LogFile logFile = getLogFile(file.getPath(), null); + this.loggingSystem.initialize(loggingInitializationContext, null, logFile); + this.logger.info("Hello world"); + assertThat(getLineWithText(file, "Hello world")).contains("INFO"); + assertThat(getRollingPolicy().getMaxHistory()).isEqualTo(30); + } + @Test public void exceptionsIncludeClassPackaging() throws Exception { this.loggingSystem.beforeInitialize(); @@ -404,6 +440,24 @@ public void initializationIsOnlyPerformedOnceUntilCleanedUp() throws Exception { verify(listener, times(2)).onReset(loggerContext); } + private static Logger getRootLogger() { + ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); + LoggerContext context = (LoggerContext) factory; + return context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + } + + private static ConsoleAppender getConsoleAppender() { + return (ConsoleAppender) getRootLogger().getAppender("CONSOLE"); + } + + private static RollingFileAppender getFileAppender() { + return (RollingFileAppender) getRootLogger().getAppender("FILE"); + } + + private static SizeAndTimeBasedRollingPolicy getRollingPolicy() { + return (SizeAndTimeBasedRollingPolicy) getFileAppender().getRollingPolicy(); + } + private String getLineWithText(File file, String outputSearch) throws Exception { return getLineWithText(FileCopyUtils.copyToString(new FileReader(file)), outputSearch);