Restore Spring Boot 1.1 logging behavior

Refactor LoggingApplicationListener and LoggingSystem to restore
Spring Boot 1.1 logging behavior. The LOG_FILE and LOG_PATH system
properties are now set before configuring the logger.

The `logging.path` property is now once again optional and will not be
used when `logging.file` is specified. The documentation has also been
updated to reflect the changes.

Fixes gh-2121
Fixes gh-2117
This commit is contained in:
Phillip Webb 2014-12-12 18:40:37 -08:00
parent 5dd40e6999
commit fc2e616cc2
16 changed files with 283 additions and 85 deletions

View File

@ -760,40 +760,35 @@ detection.
[[boot-features-logging-file-output]]
=== File output
By default, Spring Boot will only log to the console and will not write log files. If you
want to write log files in addition to the console output you need to set the
`logging.file` and/or `logging.path` properties (for example in your
`application.properties`). Log files will rotate when they reach 10 Mb.
As with console output, `ERROR`, `WARN` and `INFO` level messages are logged by default.
want to write log files in addition to the console output you need to set a
`logging.file` or `logging.path` property (for example in your `application.properties`).
The following table shows how the `logging.*` properties can be used together:
.Logging properties
[cols="1,1,1,4"]
|===
|logging.path |logging.file |Example |Description
|`logging.file` |`logging.path` |Example |Description
|_(none)_
|Exact location
|`./my.log`
|Writes to the specified file.
|_(none)_
|Simple name
|
|Console only logging.
|Specific file
|_(none)_
|`my.log`
|Writes the given file in the `temp` folder.
|Writes to the specified log file. Names can be an exact location or relative to the
current directory.
|Specific folder
|Simple name
|`/logs` & `my.log`
|Writes the given file in the specified folder.
|Specific folder
|_(none)_
|`/logs`
|Writes the `spring.log` in the specified folder.
|Specific folder
|`/var/log`
|Writes `spring.log` the specified folder. Names can be an exact location or 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.
[[boot-features-custom-log-levels]]

View File

@ -40,7 +40,7 @@ public abstract class AbstractLoggingSystem extends LoggingSystem {
}
@Override
public void initialize(String configLocation, String logFile) {
public void initialize(String configLocation, LogFile logFile) {
if (StringUtils.hasLength(configLocation)) {
// Load a specific configuration
configLocation = SystemPropertyUtils.resolvePlaceholders(configLocation);
@ -52,7 +52,7 @@ public abstract class AbstractLoggingSystem extends LoggingSystem {
// No self initialization has occurred, use defaults
loadDefaults(logFile);
}
else if (StringUtils.hasLength(logFile)) {
else if (logFile != null) {
// Self initialization has occurred but the file has changed, reload
loadConfiguration(selfInitializationConfig, logFile);
}
@ -84,14 +84,14 @@ public abstract class AbstractLoggingSystem extends LoggingSystem {
* Load sensible defaults for the logging system.
* @param logFile the file to load or {@code null} if no log file is to be written
*/
protected abstract void loadDefaults(String logFile);
protected abstract void loadDefaults(LogFile logFile);
/**
* Load a specific configuration.
* @param location the location of the configuration to load (never {@code null})
* @param logFile the file to load or {@code null} if no log file is to be written
*/
protected abstract void loadConfiguration(String location, String logFile);
protected abstract void loadConfiguration(String location, LogFile logFile);
protected final ClassLoader getClassLoader() {
return this.classLoader;

View File

@ -0,0 +1,122 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.logging;
import java.util.Properties;
import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A reference to a log output file. Log output files are specified using
* {@code logging.file} or {@code logging.path} {@link Environment} properties. If the
* {@code logging.file} property is not specified {@code "spring.log"} will be written in
* the {@code logging.path} directory.
*
* @author Phillip Webb
* @since 1.2.1
* @see #get(PropertyResolver)
*/
public class LogFile {
/**
* The name of the Spring property that contains the name of the logging configuration
* file.
*/
public static final String FILE_PROPERTY = "logging.file";
/**
* The name of the Spring property that contains the path where the logging
* configuration can be found.
*/
public static final String PATH_PROPERTY = "logging.path";
private final String file;
private final String path;
/**
* Create a new {@link LogFile} instance.
* @param file a reference to the file to write
*/
LogFile(String file) {
this(file, null);
}
/**
* Create a new {@link LogFile} instance.
* @param file a reference to the file to write
* @param path a reference to the logging path to use if {@code file} is not specified
*/
LogFile(String file, String path) {
Assert.isTrue(StringUtils.hasLength(file) || StringUtils.hasLength(path),
"File or Path must not be empty");
this.file = file;
this.path = path;
}
/**
* Apply log file details to {@code LOG_PATH} and {@code LOG_FILE} system properties.
*/
public void applyToSystemProperties() {
applyTo(System.getProperties());
}
/**
* Apply log file details to {@code LOG_PATH} and {@code LOG_FILE} map entries.
*/
public void applyTo(Properties properties) {
put(properties, "LOG_PATH", this.path);
put(properties, "LOG_FILE", toString());
}
private void put(Properties properties, String key, String value) {
if (StringUtils.hasLength(value)) {
properties.put(key, value);
}
}
@Override
public String toString() {
if (StringUtils.hasLength(this.file)) {
return this.file;
}
String path = this.path;
if (!path.endsWith("/")) {
path = path + "/";
}
return StringUtils.applyRelativePath(path, "spring.log");
}
/**
* Get a {@link LogFile} from the given Spring {@link Environment}.
* @param propertyResolver the {@link PropertyResolver} used to obtain the logging
* properties
* @return a {@link LogFile} or {@code null} if the environment didn't contain any
* suitable properties
*/
public static LogFile get(PropertyResolver propertyResolver) {
String file = propertyResolver.getProperty(FILE_PROPERTY);
String path = propertyResolver.getProperty(PATH_PROPERTY);
if (StringUtils.hasLength(file) || StringUtils.hasLength(path)) {
return new LogFile(file, path);
}
return null;
}
}

View File

@ -71,13 +71,13 @@ public class LoggingApplicationListener implements SmartApplicationListener {
* The name of the Spring property that contains the path where the logging
* configuration can be found.
*/
public static final String PATH_PROPERTY = "logging.path";
public static final String PATH_PROPERTY = LogFile.PATH_PROPERTY;
/**
* The name of the Spring property that contains the name of the logging configuration
* file.
*/
public static final String FILE_PROPERTY = "logging.file";
public static final String FILE_PROPERTY = LogFile.FILE_PROPERTY;
/**
* The name of the System property that contains the process ID.
@ -160,7 +160,7 @@ public class LoggingApplicationListener implements SmartApplicationListener {
private void initializeSystem(ConfigurableEnvironment environment,
LoggingSystem system) {
String logFile = getLogFile(environment);
LogFile logFile = LogFile.get(environment);
String logConfig = environment.getProperty(CONFIG_PROPERTY);
if (StringUtils.hasLength(logConfig)) {
try {
@ -179,24 +179,6 @@ public class LoggingApplicationListener implements SmartApplicationListener {
}
}
private String getLogFile(ConfigurableEnvironment environment) {
String file = environment.getProperty(FILE_PROPERTY);
String path = environment.getProperty(PATH_PROPERTY);
if (StringUtils.hasLength(path) || StringUtils.hasLength(file)) {
if (!StringUtils.hasLength(file)) {
file = "spring.log";
}
if (!StringUtils.hasLength(path) && !file.contains("/")) {
path = StringUtils.cleanPath(System.getProperty("java.io.tmpdir"));
}
if (StringUtils.hasLength(path)) {
return StringUtils.applyRelativePath(path, file);
}
return file;
}
return null;
}
private void initializeFinalLoggingLevels(ConfigurableEnvironment environment,
LoggingSystem system) {
if (this.springBootLogging != null) {

View File

@ -46,8 +46,8 @@ public abstract class LoggingSystem {
/**
* Reset the logging system to be limit output. This method may be called before
* {@link #initialize(String, String)} to reduce logging noise until the systems has
* been fully Initialized.
* {@link #initialize(String, LogFile)} to reduce logging noise until the
* systems has been fully Initialized.
*/
public abstract void beforeInitialize();
@ -58,7 +58,7 @@ public abstract class LoggingSystem {
* @param logFile the log output file that should be written or {@code null} for
* console only output
*/
public abstract void initialize(String configLocation, String logFile);
public abstract void initialize(String configLocation, LogFile logFile);
/**
* Sets the logging level for a given logger.

View File

@ -26,6 +26,7 @@ import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.springframework.boot.logging.AbstractLoggingSystem;
import org.springframework.boot.logging.LogFile;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.util.Assert;
@ -70,8 +71,8 @@ public class JavaLoggingSystem extends AbstractLoggingSystem {
}
@Override
protected void loadDefaults(String logFile) {
if (StringUtils.hasLength(logFile)) {
protected void loadDefaults(LogFile logFile) {
if (logFile != null) {
loadConfiguration(getPackagedConfigFile("logging-file.properties"), logFile);
}
else {
@ -80,14 +81,14 @@ public class JavaLoggingSystem extends AbstractLoggingSystem {
}
@Override
protected void loadConfiguration(String location, String logFile) {
protected void loadConfiguration(String location, LogFile logFile) {
Assert.notNull(location, "Location must not be null");
try {
String configuration = FileCopyUtils.copyToString(new InputStreamReader(
ResourceUtils.getURL(location).openStream()));
if (StringUtils.hasLength(logFile)) {
if (logFile != null) {
configuration = configuration.replace("${LOG_FILE}",
StringUtils.cleanPath(logFile));
StringUtils.cleanPath(logFile.toString()));
}
LogManager.getLogManager().readConfiguration(
new ByteArrayInputStream(configuration.getBytes()));

View File

@ -23,6 +23,7 @@ import java.util.Map;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.boot.logging.LogFile;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.logging.Slf4JLoggingSystem;
@ -67,8 +68,8 @@ public class Log4JLoggingSystem extends Slf4JLoggingSystem {
}
@Override
protected void loadDefaults(String logFile) {
if (StringUtils.hasLength(logFile)) {
protected void loadDefaults(LogFile logFile) {
if (logFile != null) {
loadConfiguration(getPackagedConfigFile("log4j-file.properties"), logFile);
}
else {
@ -77,10 +78,10 @@ public class Log4JLoggingSystem extends Slf4JLoggingSystem {
}
@Override
protected void loadConfiguration(String location, String logFile) {
protected void loadConfiguration(String location, LogFile logFile) {
Assert.notNull(location, "Location must not be null");
if (StringUtils.hasLength(logFile)) {
System.setProperty("LOG_FILE", logFile);
if (logFile != null) {
logFile.applyToSystemProperties();
}
try {
Log4jConfigurer.initLogging(location);

View File

@ -26,12 +26,12 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.springframework.boot.logging.LogFile;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.logging.Slf4JLoggingSystem;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
/**
* {@link LoggingSystem} for <a href="http://logging.apache.org/log4j/2.x/">Log4j 2</a>.
@ -71,8 +71,8 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem {
}
@Override
protected void loadDefaults(String logFile) {
if (StringUtils.hasLength(logFile)) {
protected void loadDefaults(LogFile logFile) {
if (logFile != null) {
loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile);
}
else {
@ -81,10 +81,10 @@ public class Log4J2LoggingSystem extends Slf4JLoggingSystem {
}
@Override
protected void loadConfiguration(String location, String logFile) {
protected void loadConfiguration(String location, LogFile logFile) {
Assert.notNull(location, "Location must not be null");
if (StringUtils.hasLength(logFile)) {
System.setProperty("LOG_FILE", logFile);
if (logFile != null) {
logFile.applyToSystemProperties();
}
try {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);

View File

@ -18,7 +18,7 @@ package org.springframework.boot.logging.logback;
import java.nio.charset.Charset;
import org.springframework.util.StringUtils;
import org.springframework.boot.logging.LogFile;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
@ -50,9 +50,9 @@ class DefaultLogbackConfiguration {
private static final Charset UTF8 = Charset.forName("UTF-8");
private final String logFile;
private final LogFile logFile;
public DefaultLogbackConfiguration(String logFile) {
public DefaultLogbackConfiguration(LogFile logFile) {
this.logFile = logFile;
}
@ -61,8 +61,9 @@ class DefaultLogbackConfiguration {
synchronized (config.getConfigurationLock()) {
base(config);
Appender<ILoggingEvent> consoleAppender = consoleAppender(config);
if (StringUtils.hasLength(this.logFile)) {
Appender<ILoggingEvent> fileAppender = fileAppender(config, this.logFile);
if (this.logFile != null) {
Appender<ILoggingEvent> fileAppender = fileAppender(config,
this.logFile.toString());
config.root(Level.INFO, consoleAppender, fileAppender);
}
else {

View File

@ -25,6 +25,7 @@ import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.Marker;
import org.slf4j.impl.StaticLoggerBinder;
import org.springframework.boot.logging.LogFile;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.boot.logging.Slf4JLoggingSystem;
@ -88,13 +89,13 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem {
}
@Override
public void initialize(String configLocation, String logFile) {
public void initialize(String configLocation, LogFile logFile) {
getLogger(null).getLoggerContext().getTurboFilterList().remove(FILTER);
super.initialize(configLocation, logFile);
}
@Override
protected void loadDefaults(String logFile) {
protected void loadDefaults(LogFile logFile) {
LoggerContext context = getLoggerContext();
context.stop();
context.reset();
@ -103,10 +104,10 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem {
}
@Override
protected void loadConfiguration(String location, String logFile) {
protected void loadConfiguration(String location, LogFile logFile) {
Assert.notNull(location, "Location must not be null");
if (StringUtils.hasLength(logFile)) {
System.setProperty("LOG_FILE", logFile);
if (logFile != null) {
logFile.applyToSystemProperties();
}
LoggerContext context = getLoggerContext();
context.stop();

View File

@ -60,6 +60,10 @@ public abstract class AbstractLoggingSystemTests {
System.clearProperty("PID");
}
protected final LogFile getLogFile(String file, String path) {
return new LogFile(file, path);
}
protected final String tmpDir() {
String path = StringUtils.cleanPath(System.getProperty(JAVA_IO_TMPDIR));
if (path.endsWith("/")) {

View File

@ -0,0 +1,91 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.logging;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
import org.junit.Test;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertyResolver;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link LogFile}.
*
* @author Phillip Webb
*/
public class LogFileTests {
@Test
public void noProperties() throws Exception {
PropertyResolver resolver = getPropertyResolver(null, null);
LogFile logFile = LogFile.get(resolver);
assertThat(logFile, nullValue());
}
@Test
public void loggingFile() throws Exception {
PropertyResolver resolver = getPropertyResolver("log.file", null);
LogFile logFile = LogFile.get(resolver);
Properties properties = new Properties();
logFile.applyTo(properties);
assertThat(logFile.toString(), equalTo("log.file"));
assertThat(properties.getProperty("LOG_FILE"), equalTo("log.file"));
assertThat(properties.getProperty("LOG_PATH"), nullValue());
}
@Test
public void loggingPath() throws Exception {
PropertyResolver resolver = getPropertyResolver(null, "logpath");
LogFile logFile = LogFile.get(resolver);
Properties properties = new Properties();
logFile.applyTo(properties);
assertThat(logFile.toString(), equalTo("logpath/spring.log"));
assertThat(properties.getProperty("LOG_FILE"), equalTo("logpath/spring.log"));
assertThat(properties.getProperty("LOG_PATH"), equalTo("logpath"));
}
@Test
public void loggingFileAndPath() throws Exception {
PropertyResolver resolver = getPropertyResolver("log.file", "logpath");
LogFile logFile = LogFile.get(resolver);
Properties properties = new Properties();
logFile.applyTo(properties);
assertThat(logFile.toString(), equalTo("log.file"));
assertThat(properties.getProperty("LOG_FILE"), equalTo("log.file"));
assertThat(properties.getProperty("LOG_PATH"), equalTo("logpath"));
}
private PropertyResolver getPropertyResolver(String file, String path) {
Map<String, Object> properties = new LinkedHashMap<String, Object>();
properties.put("logging.file", file);
properties.put("logging.path", path);
PropertySource<?> propertySource = new MapPropertySource("properties", properties);
MutablePropertySources propertySources = new MutablePropertySources();
propertySources.addFirst(propertySource);
return new PropertySourcesPropertyResolver(propertySources);
}
}

View File

@ -98,7 +98,7 @@ public class JavaLoggerSystemTests extends AbstractLoggingSystemTests {
}
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(null, tmpDir() + "/spring.log");
this.loggingSystem.initialize(null, getLogFile(null, tmpDir()));
this.logger.info("Hello world");
String output = this.output.toString().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));

View File

@ -69,7 +69,7 @@ public class Log4JLoggingSystemTests extends AbstractLoggingSystemTests {
public void withFile() throws Exception {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(null, tmpDir() + "/spring.log");
this.loggingSystem.initialize(null, getLogFile(null, tmpDir()));
this.logger.info("Hello world");
String output = this.output.toString().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));
@ -80,8 +80,8 @@ public class Log4JLoggingSystemTests extends AbstractLoggingSystemTests {
@Test
public void testNonDefaultConfigLocation() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize("classpath:log4j-nondefault.properties", tmpDir()
+ "/spring.log");
this.loggingSystem.initialize("classpath:log4j-nondefault.properties",
getLogFile(null, tmpDir()));
this.logger.info("Hello world");
String output = this.output.toString().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));

View File

@ -71,7 +71,7 @@ public class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
public void withFile() throws Exception {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(null, tmpDir() + "/spring.log");
this.loggingSystem.initialize(null, getLogFile(null, tmpDir()));
this.logger.info("Hello world");
String output = this.output.toString().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));
@ -82,8 +82,8 @@ public class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
@Test
public void testNonDefaultConfigLocation() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize("classpath:log4j2-nondefault.xml", tmpDir()
+ "/tmp.log");
this.loggingSystem.initialize("classpath:log4j2-nondefault.xml",
getLogFile(tmpDir() + "/tmp.log", null));
this.logger.info("Hello world");
String output = this.output.toString().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));

View File

@ -78,7 +78,7 @@ public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
public void withFile() throws Exception {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(null, tmpDir() + "/spring.log");
this.loggingSystem.initialize(null, getLogFile(null, tmpDir()));
this.logger.info("Hello world");
String output = this.output.toString().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));
@ -98,8 +98,8 @@ public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
@Test
public void testNonDefaultConfigLocation() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize("classpath:logback-nondefault.xml", tmpDir()
+ "/tmp.log");
this.loggingSystem.initialize("classpath:logback-nondefault.xml",
getLogFile(tmpDir() + "/tmp.log", null));
this.logger.info("Hello world");
String output = this.output.toString().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));