Skip to content

Commit 125fd0b

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 b9b284d commit 125fd0b

File tree

7 files changed

+126
-31
lines changed

7 files changed

+126
-31
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
@@ -1496,8 +1496,13 @@ current directory.
14961496
relative to the current directory.
14971497
|===
14981498

1499-
Log files will rotate when they reach 10 MB and as with console output, `ERROR`, `WARN`
1500-
and `INFO` level messages are logged by default.
1499+
Log files will rotate daily and, in addition to time limit, when they reach the size limit
1500+
which is 10 MB by default. Size limit can be changed using `logging.file.max-size`
1501+
property. By default, archived log file is unbounded meaning entire history will be
1502+
preserved. You can use `logging.file.max-history` property to limit the number of archive
1503+
files to keep.
1504+
1505+
As with console output, `ERROR`, `WARN` and `INFO` level messages are logged by default.
15011506

15021507
NOTE: The logging system is initialized early in the application lifecycle and as such
15031508
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
15841589
|`LOG_FILE`
15851590
|Used in default log configuration if defined.
15861591

1592+
|`logging.file.max-size`
1593+
|`LOG_FILE_MAX_SIZE`
1594+
|Maximum log file size (if LOG_FILE enabled). (Only supported with the default logback setup.)
1595+
1596+
|`logging.file.max-history`
1597+
|`LOG_FILE_MAX_HISTORY`
1598+
|Maximum number of archive log files to keep (if LOG_FILE enabled). (Only supported with the default logback setup.)
1599+
15871600
|`logging.path`
15881601
|`LOG_PATH`
15891602
|Used in default log configuration if defined.

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
* @author Andy Wilkinson
3030
* @author Phillip Webb
3131
* @author Madhura Bhave
32+
* @author Vedran Pavic
3233
* @since 2.0.0
3334
*/
3435
public class LoggingSystemProperties {
@@ -63,6 +64,16 @@ public class LoggingSystemProperties {
6364
*/
6465
public static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN";
6566

67+
/**
68+
* The name of the System property that contains the file log max history.
69+
*/
70+
public static final String FILE_MAX_HISTORY = "LOG_FILE_MAX_HISTORY";
71+
72+
/**
73+
* The name of the System property that contains the file log max size.
74+
*/
75+
public static final String FILE_MAX_SIZE = "LOG_FILE_MAX_SIZE";
76+
6677
/**
6778
* The name of the System property that contains the log level pattern.
6879
*/
@@ -89,6 +100,8 @@ public void apply(LogFile logFile) {
89100
"exception-conversion-word");
90101
setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "pattern.console");
91102
setSystemProperty(resolver, FILE_LOG_PATTERN, "pattern.file");
103+
setSystemProperty(resolver, FILE_MAX_HISTORY, "file.max-history");
104+
setSystemProperty(resolver, FILE_MAX_SIZE, "file.max-size");
92105
setSystemProperty(resolver, LOG_LEVEL_PATTERN, "pattern.level");
93106
setSystemProperty(PID_KEY, new ApplicationPid().toString());
94107
if (logFile != null) {

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

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
import ch.qos.logback.classic.spi.ILoggingEvent;
2525
import ch.qos.logback.core.Appender;
2626
import ch.qos.logback.core.ConsoleAppender;
27-
import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
27+
import ch.qos.logback.core.CoreConstants;
2828
import ch.qos.logback.core.rolling.RollingFileAppender;
29-
import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
29+
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
3030
import ch.qos.logback.core.util.FileSize;
3131
import ch.qos.logback.core.util.OptionHelper;
3232

@@ -45,6 +45,7 @@
4545
*
4646
* @author Phillip Webb
4747
* @author Madhura Bhave
48+
* @author Vedran Pavic
4849
* @since 1.1.2
4950
*/
5051
class DefaultLogbackConfiguration {
@@ -57,6 +58,8 @@ class DefaultLogbackConfiguration {
5758
private static final String FILE_LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} "
5859
+ "${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}";
5960

61+
private static final String MAX_FILE_SIZE = "10MB";
62+
6063
private static final Charset UTF8 = Charset.forName("UTF-8");
6164

6265
private final PropertyResolver patterns;
@@ -140,34 +143,32 @@ private Appender<ILoggingEvent> fileAppender(LogbackConfigurator config,
140143
config.start(encoder);
141144
appender.setFile(logFile);
142145
setRollingPolicy(appender, config, logFile);
143-
setMaxFileSize(appender, config);
144146
config.appender("FILE", appender);
145147
return appender;
146148
}
147149

148150
private void setRollingPolicy(RollingFileAppender<ILoggingEvent> appender,
149151
LogbackConfigurator config, String logFile) {
150-
FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
151-
rollingPolicy.setFileNamePattern(logFile + ".%i");
152-
appender.setRollingPolicy(rollingPolicy);
153-
rollingPolicy.setParent(appender);
154-
config.start(rollingPolicy);
155-
}
156-
157-
private void setMaxFileSize(RollingFileAppender<ILoggingEvent> appender,
158-
LogbackConfigurator config) {
159-
SizeBasedTriggeringPolicy<ILoggingEvent> triggeringPolicy = new SizeBasedTriggeringPolicy<>();
152+
SizeAndTimeBasedRollingPolicy<ILoggingEvent> rollingPolicy =
153+
new SizeAndTimeBasedRollingPolicy<>();
154+
rollingPolicy.setFileNamePattern(logFile + ".%d{yyyy-MM-dd}.%i.gz");
155+
String maxFileSize = this.patterns.getProperty("logging.file.max-size",
156+
MAX_FILE_SIZE);
160157
try {
161-
triggeringPolicy.setMaxFileSize(FileSize.valueOf("10MB"));
158+
rollingPolicy.setMaxFileSize(FileSize.valueOf(maxFileSize));
162159
}
163160
catch (NoSuchMethodError ex) {
164161
// Logback < 1.1.8 used String configuration
165-
Method method = ReflectionUtils.findMethod(SizeBasedTriggeringPolicy.class,
166-
"setMaxFileSize", String.class);
167-
ReflectionUtils.invokeMethod(method, triggeringPolicy, "10MB");
162+
Method method = ReflectionUtils.findMethod(
163+
SizeAndTimeBasedRollingPolicy.class, "setMaxFileSize", String.class);
164+
ReflectionUtils.invokeMethod(method, rollingPolicy, maxFileSize);
168165
}
169-
appender.setTriggeringPolicy(triggeringPolicy);
170-
config.start(triggeringPolicy);
166+
int maxHistory = this.patterns.getProperty("logging.file.max-history",
167+
Integer.class, CoreConstants.UNBOUND_HISTORY);
168+
rollingPolicy.setMaxHistory(maxHistory);
169+
appender.setRollingPolicy(rollingPolicy);
170+
rollingPolicy.setParent(appender);
171+
config.start(rollingPolicy);
171172
}
172173

173174
}

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: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
import ch.qos.logback.classic.Logger;
2828
import ch.qos.logback.classic.LoggerContext;
2929
import ch.qos.logback.classic.spi.LoggerContextListener;
30+
import ch.qos.logback.core.ConsoleAppender;
31+
import ch.qos.logback.core.CoreConstants;
32+
import ch.qos.logback.core.rolling.RollingFileAppender;
33+
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
3034
import org.apache.commons.logging.Log;
3135
import org.apache.commons.logging.impl.SLF4JLogFactory;
3236
import org.hamcrest.Matcher;
@@ -48,6 +52,7 @@
4852
import org.springframework.boot.testsupport.assertj.Matched;
4953
import org.springframework.boot.testsupport.rule.OutputCapture;
5054
import org.springframework.mock.env.MockEnvironment;
55+
import org.springframework.test.util.ReflectionTestUtils;
5156
import org.springframework.util.FileCopyUtils;
5257
import org.springframework.util.StringUtils;
5358

@@ -66,6 +71,7 @@
6671
* @author Andy Wilkinson
6772
* @author Ben Hale
6873
* @author Madhura Bhave
74+
* @author Vedran Pavic
6975
*/
7076
public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
7177

@@ -120,15 +126,16 @@ public void withFile() throws Exception {
120126
assertThat(getLineWithText(output, "Hello world")).contains("INFO");
121127
assertThat(file.exists()).isTrue();
122128
assertThat(getLineWithText(file, "Hello world")).contains("INFO");
129+
assertThat(ReflectionTestUtils.getField(getRollingPolicy(), "maxFileSize")
130+
.toString()).isEqualTo("10 MB");
131+
assertThat(getRollingPolicy().getMaxHistory()).isEqualTo(
132+
CoreConstants.UNBOUND_HISTORY);
123133
}
124134

125135
@Test
126136
public void testBasicConfigLocation() throws Exception {
127137
this.loggingSystem.beforeInitialize();
128-
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
129-
LoggerContext context = (LoggerContext) factory;
130-
Logger root = context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
131-
assertThat(root.getAppender("CONSOLE")).isNotNull();
138+
assertThat(getConsoleAppender()).isNotNull();
132139
}
133140

134141
@Test
@@ -339,6 +346,35 @@ public void testFilePatternProperty() throws Exception {
339346
assertThat(getLineWithText(file, "Hello world")).doesNotContain("INFO");
340347
}
341348

349+
@Test
350+
public void testMaxFileSizeProperty() throws Exception {
351+
MockEnvironment environment = new MockEnvironment();
352+
environment.setProperty("logging.file.max-size", "100MB");
353+
LoggingInitializationContext loggingInitializationContext =
354+
new LoggingInitializationContext(environment);
355+
File file = new File(tmpDir(), "logback-test.log");
356+
LogFile logFile = getLogFile(file.getPath(), null);
357+
this.loggingSystem.initialize(loggingInitializationContext, null, logFile);
358+
this.logger.info("Hello world");
359+
assertThat(getLineWithText(file, "Hello world")).contains("INFO");
360+
assertThat(ReflectionTestUtils.getField(getRollingPolicy(), "maxFileSize")
361+
.toString()).isEqualTo("100 MB");
362+
}
363+
364+
@Test
365+
public void testMaxHistoryProperty() throws Exception {
366+
MockEnvironment environment = new MockEnvironment();
367+
environment.setProperty("logging.file.max-history", "30");
368+
LoggingInitializationContext loggingInitializationContext =
369+
new LoggingInitializationContext(environment);
370+
File file = new File(tmpDir(), "logback-test.log");
371+
LogFile logFile = getLogFile(file.getPath(), null);
372+
this.loggingSystem.initialize(loggingInitializationContext, null, logFile);
373+
this.logger.info("Hello world");
374+
assertThat(getLineWithText(file, "Hello world")).contains("INFO");
375+
assertThat(getRollingPolicy().getMaxHistory()).isEqualTo(30);
376+
}
377+
342378
@Test
343379
public void exceptionsIncludeClassPackaging() throws Exception {
344380
this.loggingSystem.beforeInitialize();
@@ -404,6 +440,24 @@ public void initializationIsOnlyPerformedOnceUntilCleanedUp() throws Exception {
404440
verify(listener, times(2)).onReset(loggerContext);
405441
}
406442

443+
private static Logger getRootLogger() {
444+
ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory();
445+
LoggerContext context = (LoggerContext) factory;
446+
return context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
447+
}
448+
449+
private static ConsoleAppender getConsoleAppender() {
450+
return (ConsoleAppender) getRootLogger().getAppender("CONSOLE");
451+
}
452+
453+
private static RollingFileAppender getFileAppender() {
454+
return (RollingFileAppender) getRootLogger().getAppender("FILE");
455+
}
456+
457+
private static SizeAndTimeBasedRollingPolicy getRollingPolicy() {
458+
return (SizeAndTimeBasedRollingPolicy) getFileAppender().getRollingPolicy();
459+
}
460+
407461
private String getLineWithText(File file, String outputSearch) throws Exception {
408462
return getLineWithText(FileCopyUtils.copyToString(new FileReader(file)),
409463
outputSearch);

0 commit comments

Comments
 (0)