Skip to content

Commit 9cdead7

Browse files
committed
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.
1 parent 0e00a49 commit 9cdead7

File tree

8 files changed

+135
-30
lines changed

8 files changed

+135
-30
lines changed

spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ content into your application; rather pick only the properties that you need.
4141
logging.config= # Location of the logging configuration file. For instance `classpath:logback.xml` for Logback
4242
logging.exception-conversion-word=%wEx # Conversion word used when logging exceptions.
4343
logging.file= # Log file name. For instance `myapp.log`
44+
logging.file.max-history= # Maximum of archive log files to keep. Only supported with the default logback setup.
45+
logging.file.max-size= # Maximum log file size. Only supported with the default logback setup.
4446
logging.level.*= # Log levels severity mapping. For instance `logging.level.org.springframework=DEBUG`
4547
logging.path= # Location of the log file. For instance `/var/log`
4648
logging.pattern.console= # Appender pattern for output to the console. Only supported with the default logback setup.

spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1304,8 +1304,13 @@ current directory.
13041304
relative to the current directory.
13051305
|===
13061306

1307-
Log files will rotate when they reach 10 Mb and as with console output, `ERROR`, `WARN`
1308-
and `INFO` level messages are logged by default.
1307+
Log files will rotate daily and, in addition to time limit, when they reach the size limit
1308+
which is 10 MB by default. Size limit can be changed using `logging.file.max-size`
1309+
property. By default, archived log file is unbounded meaning entire history will be
1310+
preserved. You can use `logging.file.max-history` property to limit the number of archive
1311+
files to keep.
1312+
1313+
As with console output, `ERROR`, `WARN` and `INFO` level messages are logged by default.
13091314

13101315
NOTE: The logging system is initialized early in the application lifecycle and as such
13111316
logging properties will not be found in property files loaded via `@PropertySource`
@@ -1392,6 +1397,14 @@ To help with the customization some other properties are transferred from the Sp
13921397
|`LOG_FILE`
13931398
|Used in default log configuration if defined.
13941399

1400+
|`logging.file.max-size`
1401+
|`LOG_FILE_MAX_SIZE`
1402+
|Maximum log file size (if LOG_FILE enabled). (Only supported with the default logback setup.)
1403+
1404+
|`logging.file.max-history`
1405+
|`LOG_FILE_MAX_HISTORY`
1406+
|Maximum number of archive log files to keep (if LOG_FILE enabled). (Only supported with the default logback setup.)
1407+
13951408
|`logging.path`
13961409
|`LOG_PATH`
13971410
|Used in default log configuration if defined.

spring-boot/src/main/java/org/springframework/boot/logging/LoggingApplicationListener.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,16 @@ public class LoggingApplicationListener implements GenericApplicationListener {
136136
*/
137137
public static final String FILE_LOG_PATTERN = LoggingSystemProperties.FILE_LOG_PATTERN;
138138

139+
/**
140+
* The name of the System property that contains the file log max history.
141+
*/
142+
public static final String FILE_MAX_HISTORY = LoggingSystemProperties.FILE_MAX_HISTORY;
143+
144+
/**
145+
* The name of the System property that contains the file log max size.
146+
*/
147+
public static final String FILE_MAX_SIZE = LoggingSystemProperties.FILE_MAX_SIZE;
148+
139149
/**
140150
* The name of the System property that contains the log level pattern.
141151
*/

spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ class LoggingSystemProperties {
3636

3737
static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN";
3838

39+
static final String FILE_MAX_HISTORY = "LOG_FILE_MAX_HISTORY";
40+
41+
static final String FILE_MAX_SIZE = "LOG_FILE_MAX_SIZE";
42+
3943
static final String LOG_LEVEL_PATTERN = "LOG_LEVEL_PATTERN";
4044

4145
private final Environment environment;
@@ -55,6 +59,8 @@ public void apply(LogFile logFile) {
5559
"exception-conversion-word");
5660
setSystemProperty(propertyResolver, CONSOLE_LOG_PATTERN, "pattern.console");
5761
setSystemProperty(propertyResolver, FILE_LOG_PATTERN, "pattern.file");
62+
setSystemProperty(propertyResolver, FILE_MAX_HISTORY, "file.max-history");
63+
setSystemProperty(propertyResolver, FILE_MAX_SIZE, "file.max-size");
5864
setSystemProperty(propertyResolver, LOG_LEVEL_PATTERN, "pattern.level");
5965
setSystemProperty(PID_KEY, new ApplicationPid().toString());
6066
if (logFile != null) {

spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2015 the original author or authors.
2+
* Copyright 2012-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,9 +23,9 @@
2323
import ch.qos.logback.classic.spi.ILoggingEvent;
2424
import ch.qos.logback.core.Appender;
2525
import ch.qos.logback.core.ConsoleAppender;
26-
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
26+
import ch.qos.logback.core.CoreConstants;
2727
import ch.qos.logback.core.rolling.RollingFileAppender;
28-
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
28+
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
2929
import ch.qos.logback.core.util.OptionHelper;
3030

3131
import org.springframework.boot.bind.RelaxedPropertyResolver;
@@ -41,6 +41,7 @@
4141
* and {@code file-appender.xml} files provided for classic {@code logback.xml} use.
4242
*
4343
* @author Phillip Webb
44+
* @author Vedran Pavic
4445
* @since 1.1.2
4546
*/
4647
class DefaultLogbackConfiguration {
@@ -53,23 +54,25 @@ class DefaultLogbackConfiguration {
5354
private static final String FILE_LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} "
5455
+ "${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}";
5556

57+
private static final String MAX_FILE_SIZE = "10MB";
58+
5659
private static final Charset UTF8 = Charset.forName("UTF-8");
5760

58-
private final PropertyResolver patterns;
61+
private final PropertyResolver resolver;
5962

6063
private final LogFile logFile;
6164

6265
DefaultLogbackConfiguration(LoggingInitializationContext initializationContext,
6366
LogFile logFile) {
64-
this.patterns = getPatternsResolver(initializationContext.getEnvironment());
67+
this.resolver = getLoggingResolver(initializationContext.getEnvironment());
6568
this.logFile = logFile;
6669
}
6770

68-
private PropertyResolver getPatternsResolver(Environment environment) {
71+
private PropertyResolver getLoggingResolver(Environment environment) {
6972
if (environment == null) {
7073
return new PropertySourcesPropertyResolver(null);
7174
}
72-
return new RelaxedPropertyResolver(environment, "logging.pattern.");
75+
return new RelaxedPropertyResolver(environment, "logging.");
7376
}
7477

7578
public void apply(LogbackConfigurator config) {
@@ -115,7 +118,8 @@ private void base(LogbackConfigurator config) {
115118
private Appender<ILoggingEvent> consoleAppender(LogbackConfigurator config) {
116119
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<ILoggingEvent>();
117120
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
118-
String logPattern = this.patterns.getProperty("console", CONSOLE_LOG_PATTERN);
121+
String logPattern = this.resolver.getProperty("pattern.console",
122+
CONSOLE_LOG_PATTERN);
119123
encoder.setPattern(OptionHelper.substVars(logPattern, config.getContext()));
120124
encoder.setCharset(UTF8);
121125
config.start(encoder);
@@ -126,26 +130,28 @@ private Appender<ILoggingEvent> consoleAppender(LogbackConfigurator config) {
126130

127131
private Appender<ILoggingEvent> fileAppender(LogbackConfigurator config,
128132
String logFile) {
129-
RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<ILoggingEvent>();
133+
RollingFileAppender<ILoggingEvent> appender =
134+
new RollingFileAppender<ILoggingEvent>();
130135
PatternLayoutEncoder encoder = new PatternLayoutEncoder();
131-
String logPattern = this.patterns.getProperty("file", FILE_LOG_PATTERN);
136+
String logPattern = this.resolver.getProperty("pattern.file", FILE_LOG_PATTERN);
132137
encoder.setPattern(OptionHelper.substVars(logPattern, config.getContext()));
133138
appender.setEncoder(encoder);
134139
config.start(encoder);
135140

136141
appender.setFile(logFile);
137142

138-
FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
139-
rollingPolicy.setFileNamePattern(logFile + ".%i");
143+
SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy =
144+
new SizeAndTimeBasedRollingPolicy<ILoggingEvent>();
145+
rollingPolicy.setFileNamePattern(logFile + ".%d{yyyy-MM-dd}.%i.gz");
146+
String maxFileSize = this.resolver.getProperty("file.max-size",
147+
MAX_FILE_SIZE);
148+
rollingPolicy.setMaxFileSize(maxFileSize);
149+
int maxHistory = this.resolver.getProperty("file.max-history", Integer.class,
150+
CoreConstants.UNBOUND_HISTORY);
151+
rollingPolicy.setMaxHistory(maxHistory);
140152
appender.setRollingPolicy(rollingPolicy);
141153
rollingPolicy.setParent(appender);
142154
config.start(rollingPolicy);
143-
144-
SizeBasedTriggeringPolicy<ILoggingEvent> triggeringPolicy = new SizeBasedTriggeringPolicy<ILoggingEvent>();
145-
triggeringPolicy.setMaxFileSize("10MB");
146-
appender.setTriggeringPolicy(triggeringPolicy);
147-
config.start(triggeringPolicy);
148-
149155
config.appender("FILE", appender);
150156
return appender;
151157
}

spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@
6969
"description": "Name of the log file. Names can be an exact location or relative to the current directory.",
7070
"sourceType": "org.springframework.boot.logging.LoggingApplicationListener"
7171
},
72+
{
73+
"name": "logging.file.max-size",
74+
"type": "java.lang.String",
75+
"description": "Maximum log file size. Only supported with the default logback setup.",
76+
"sourceType": "org.springframework.boot.logging.LoggingApplicationListener",
77+
"defaultValue": "10MB"
78+
},
79+
{
80+
"name": "logging.file.max-history",
81+
"type": "java.lang.Integer",
82+
"description": "Maximum number of archive log files to keep. Only supported with the default logback setup.",
83+
"sourceType": "org.springframework.boot.logging.LoggingApplicationListener",
84+
"defaultValue": 0
85+
},
7286
{
7387
"name": "logging.level",
7488
"type": "java.util.Map<java.lang.String,java.lang.String>",

spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,10 @@ initialization performed by Boot
1212
<pattern>${FILE_LOG_PATTERN}</pattern>
1313
</encoder>
1414
<file>${LOG_FILE}</file>
15-
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
16-
<fileNamePattern>${LOG_FILE}.%i</fileNamePattern>
15+
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
16+
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz</fileNamePattern>
17+
<maxFileSize>${MAX_FILE_SIZE:-10MB}</maxFileSize>
18+
<maxHistory>${MAX_HISTORY:-0}</maxHistory>
1719
</rollingPolicy>
18-
<triggeringPolicy
19-
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
20-
<MaxFileSize>10MB</MaxFileSize>
21-
</triggeringPolicy>
2220
</appender>
2321
</included>

spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
import ch.qos.logback.classic.Logger;
2525
import ch.qos.logback.classic.LoggerContext;
2626
import ch.qos.logback.classic.spi.LoggerContextListener;
27+
import ch.qos.logback.core.ConsoleAppender;
28+
import ch.qos.logback.core.CoreConstants;
29+
import ch.qos.logback.core.rolling.RollingFileAppender;
30+
import ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP;
31+
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
2732
import org.apache.commons.logging.Log;
2833
import org.apache.commons.logging.impl.SLF4JLogFactory;
2934
import org.hamcrest.Matcher;
@@ -59,6 +64,7 @@
5964
* @author Dave Syer
6065
* @author Phillip Webb
6166
* @author Andy Wilkinson
67+
* @author Vedran Pavic
6268
*/
6369
public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
6470

@@ -113,15 +119,17 @@ public void withFile() throws Exception {
113119
assertThat(getLineWithText(output, "Hello world")).contains("INFO");
114120
assertThat(file.exists()).isTrue();
115121
assertThat(getLineWithText(file, "Hello world")).contains("INFO");
122+
SizeAndTimeBasedFNATP triggeringPolicy = (SizeAndTimeBasedFNATP) getRollingPolicy()
123+
.getTimeBasedFileNamingAndTriggeringPolicy();
124+
assertThat(triggeringPolicy.getMaxFileSize()).isEqualTo("10MB");
125+
assertThat(getRollingPolicy().getMaxHistory()).isEqualTo(
126+
CoreConstants.UNBOUND_HISTORY);
116127
}
117128

118129
@Test
119130
public void testBasicConfigLocation() throws Exception {
120131
this.loggingSystem.beforeInitialize();
121-
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
122-
LoggerContext context = (LoggerContext) factory;
123-
Logger root = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
124-
assertThat(root.getAppender("CONSOLE")).isNotNull();
132+
assertThat(getConsoleAppender()).isNotNull();
125133
}
126134

127135
@Test
@@ -272,6 +280,36 @@ public void testFilePatternProperty() throws Exception {
272280
assertThat(getLineWithText(file, "Hello world")).doesNotContain("INFO");
273281
}
274282

283+
@Test
284+
public void testMaxFileSizeProperty() throws Exception {
285+
MockEnvironment environment = new MockEnvironment();
286+
environment.setProperty("logging.file.max-size", "100MB");
287+
LoggingInitializationContext loggingInitializationContext =
288+
new LoggingInitializationContext(environment);
289+
File file = new File(tmpDir(), "logback-test.log");
290+
LogFile logFile = getLogFile(file.getPath(), null);
291+
this.loggingSystem.initialize(loggingInitializationContext, null, logFile);
292+
this.logger.info("Hello world");
293+
assertThat(getLineWithText(file, "Hello world")).contains("INFO");
294+
SizeAndTimeBasedFNATP triggeringPolicy = (SizeAndTimeBasedFNATP) getRollingPolicy()
295+
.getTimeBasedFileNamingAndTriggeringPolicy();
296+
assertThat(triggeringPolicy.getMaxFileSize()).isEqualTo("100MB");
297+
}
298+
299+
@Test
300+
public void testMaxHistoryProperty() throws Exception {
301+
MockEnvironment environment = new MockEnvironment();
302+
environment.setProperty("logging.file.max-history", "30");
303+
LoggingInitializationContext loggingInitializationContext =
304+
new LoggingInitializationContext(environment);
305+
File file = new File(tmpDir(), "logback-test.log");
306+
LogFile logFile = getLogFile(file.getPath(), null);
307+
this.loggingSystem.initialize(loggingInitializationContext, null, logFile);
308+
this.logger.info("Hello world");
309+
assertThat(getLineWithText(file, "Hello world")).contains("INFO");
310+
assertThat(getRollingPolicy().getMaxHistory()).isEqualTo(30);
311+
}
312+
275313
@Test
276314
public void exceptionsIncludeClassPackaging() throws Exception {
277315
this.loggingSystem.beforeInitialize();
@@ -337,6 +375,24 @@ public void initializationIsOnlyPerformedOnceUntilCleanedUp() throws Exception {
337375
verify(listener, times(2)).onReset(loggerContext);
338376
}
339377

378+
private static Logger getRootLogger() {
379+
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
380+
LoggerContext context = (LoggerContext) factory;
381+
return context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
382+
}
383+
384+
private static ConsoleAppender getConsoleAppender() {
385+
return (ConsoleAppender) getRootLogger().getAppender("CONSOLE");
386+
}
387+
388+
private static RollingFileAppender getFileAppender() {
389+
return (RollingFileAppender) getRootLogger().getAppender("FILE");
390+
}
391+
392+
private static SizeAndTimeBasedRollingPolicy getRollingPolicy() {
393+
return (SizeAndTimeBasedRollingPolicy) getFileAppender().getRollingPolicy();
394+
}
395+
340396
private String getLineWithText(File file, String outputSearch) throws Exception {
341397
return getLineWithText(FileCopyUtils.copyToString(new FileReader(file)),
342398
outputSearch);

0 commit comments

Comments
 (0)