Provide a property to configure conversion word used to log exceptions

This commit adds a new property, logging.exception-conversion-word,
that can be used to configure the conversion word that is used when
logging exceptions. The default value, %rEx, will log exceptions
with the root cause first and include class packaging information in
the stack trace. The new property is supported when using either
Logback or Log4J2.

Closes gh-3684
pull/3652/merge
Andy Wilkinson 9 years ago
parent 67d29c13f5
commit 1a11ed20ba

@ -56,10 +56,11 @@ content into your application; rather pick only the properties that you need.
spring.output.ansi.enabled=detect # Configure the ANSI output ("detect", "always", "never")
# LOGGING
logging.path=/var/log
logging.file=myapp.log
logging.config= # location of config file (default classpath:logback.xml for logback)
logging.exception-conversion-word=%rEx # conversion word used when logging exceptions
logging.file=myapp.log
logging.level.*= # levels for loggers, e.g. "logging.level.org.springframework=DEBUG" (TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF)
logging.path=/var/log
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)

@ -1196,6 +1196,8 @@ These are:
* `${LOG_FILE}` if `logging.file` was set in Boot's external configuration.
* `${LOG_PATH}` if `logging.path` was set (representing a directory for
log files to live in).
* `${LOG_EXCEPTION_CONVERSION_WORD}` if `logging.exception-conversion-word` was set in
Boot's external configuration.
Spring Boot also provides some nice ANSI colour terminal output on a console (but not in
a log file) using a custom Logback converter. See the default `base.xml` configuration

@ -1068,6 +1068,10 @@ To help with the customization some other properties are transferred from the Sp
|===
|Spring Environment |System Property |Comments
|`logging.exception-conversion-word`
|`LOG_EXCEPTION_CONVERSION_WORD`
|The conversion word that's used when logging exceptions.
|`logging.file`
|`LOG_FILE`
|Used in default log configuration if defined.

@ -92,6 +92,11 @@ public class LoggingApplicationListener implements GenericApplicationListener {
*/
public static final String PID_KEY = "PID";
/**
* The name of the System property that contains the exception conversion word.
*/
public static final String EXCEPTION_CONVERSION_WORD = "LOG_EXCEPTION_CONVERSION_WORD";
private static MultiValueMap<LogLevel, String> LOG_LEVEL_LOGGERS;
static {
LOG_LEVEL_LOGGERS = new LinkedMultiValueMap<LogLevel, String>();
@ -185,6 +190,10 @@ public class LoggingApplicationListener implements GenericApplicationListener {
if (System.getProperty(PID_KEY) == null) {
System.setProperty(PID_KEY, new ApplicationPid().toString());
}
if (System.getProperty(EXCEPTION_CONVERSION_WORD) == null) {
System.setProperty(EXCEPTION_CONVERSION_WORD,
environment.getProperty("logging.exception-conversion-word", "%rEx"));
}
initializeEarlyLoggingLevel(environment);
initializeSystem(environment, this.loggingSystem);
initializeFinalLoggingLevels(environment, this.loggingSystem);

@ -48,10 +48,10 @@ class DefaultLogbackConfiguration {
private static final String CONSOLE_LOG_PATTERN = "%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} "
+ "%clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} "
+ "%clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} "
+ "%clr(:){faint} %m%n%rEx";
+ "%clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%rEx}";
private static final String FILE_LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} %5p "
+ "${PID:- } --- [%t] %-40.40logger{39} : %m%n%rEx";
+ "${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%rEx}";
private static final Charset UTF8 = Charset.forName("UTF-8");

@ -2,7 +2,8 @@
<Configuration status="WARN">
<Properties>
<Property name="PID">????</Property>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${sys:PID} --- [%t] %-40.40c{1.} : %m%n%rEx</Property>
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%rEx</Property>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${sys:PID} --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">

@ -2,7 +2,8 @@
<Configuration status="WARN">
<Properties>
<Property name="PID">????</Property>
<Property name="LOG_PATTERN">%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n%rEx</Property>
<Property name="LOG_EXCEPTION_CONVERSION_WORD">%rEx</Property>
<Property name="LOG_PATTERN">%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">

@ -9,8 +9,8 @@ initialization performed by Boot
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%rEx"/>
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%t] %-40.40logger{39} : %m%n%rEx"/>
<property name="CONSOLE_LOG_PATTERN" value="%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%rEx}"/>
<property name="FILE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%rEx}"/>
<appender name="DEBUG_LEVEL_REMAPPER" class="org.springframework.boot.logging.logback.LevelRemappingAppender">
<destinationLogger>org.springframework.boot</destinationLogger>

@ -89,6 +89,7 @@ public class LoggingApplicationListenerTests {
System.clearProperty("LOG_FILE");
System.clearProperty("LOG_PATH");
System.clearProperty("PID");
System.clearProperty("LOG_EXCEPTION_CONVERSION_WORD");
if (this.context != null) {
this.context.close();
}
@ -109,10 +110,10 @@ public class LoggingApplicationListenerTests {
public void baseConfigLocation() {
this.initializer.initialize(this.context.getEnvironment(),
this.context.getClassLoader());
this.logger.info("Hello world");
String output = this.outputCapture.toString().trim();
assertTrue("Wrong output:\n" + output, output.contains("Hello world"));
assertFalse("Wrong output:\n" + output, output.contains("???"));
this.outputCapture.expect(containsString("Hello world"));
this.outputCapture.expect(not(containsString("???")));
this.outputCapture.expect(containsString("[junit-"));
this.logger.info("Hello world", new RuntimeException("Expected"));
assertFalse(new File(tmpDir() + "/spring.log").exists());
}
@ -328,6 +329,18 @@ public class LoggingApplicationListenerTests {
assertFalse(bridgeHandlerInstalled());
}
@Test
public void overrideExceptionConversionWord() throws Exception {
EnvironmentTestUtils.addEnvironment(this.context,
"logging.exception-conversion-word:%ex");
this.initializer.initialize(this.context.getEnvironment(),
this.context.getClassLoader());
this.outputCapture.expect(containsString("Hello world"));
this.outputCapture.expect(not(containsString("???")));
this.outputCapture.expect(not(containsString("[junit-")));
this.logger.info("Hello world", new RuntimeException("Expected"));
}
private boolean bridgeHandlerInstalled() {
Logger rootLogger = LogManager.getLogManager().getLogger("");
Handler[] handlers = rootLogger.getHandlers();

@ -17,6 +17,7 @@
package org.springframework.boot.logging.log4j2;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -25,6 +26,8 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.FileConfigurationMonitor;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
@ -32,6 +35,7 @@ import org.junit.Test;
import org.springframework.boot.logging.AbstractLoggingSystemTests;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.OutputCapture;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -189,21 +193,49 @@ public class Log4J2LoggingSystemTests extends AbstractLoggingSystemTests {
@Test
public void exceptionsIncludeClassPackaging() throws Exception {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(null, null, null);
this.output.expect(containsString("[junit-"));
this.loggingSystem.initialize(null, null, getLogFile(null, tmpDir()));
Matcher<String> expectedOutput = containsString("[junit-");
this.output.expect(expectedOutput);
this.logger.warn("Expected exception", new RuntimeException("Expected"));
String fileContents = FileCopyUtils.copyToString(new FileReader(new File(tmpDir()
+ "/spring.log")));
assertThat(fileContents, is(expectedOutput));
}
@Test
public void rootCauseIsLoggedFirst() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(null, null, getLogFile(null, tmpDir()));
Matcher<String> expectedOutput = containsString("Wrapped by: "
+ "java.lang.RuntimeException: Expected");
this.output.expect(expectedOutput);
this.logger.warn("Expected exception", new RuntimeException("Expected",
new RuntimeException("Cause")));
String fileContents = FileCopyUtils.copyToString(new FileReader(new File(tmpDir()
+ "/spring.log")));
assertThat(fileContents, is(expectedOutput));
}
@Test
public void customExceptionConversionWord() throws Exception {
System.setProperty("LOG_EXCEPTION_CONVERSION_WORD", "%ex");
try {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(null, null, null);
this.output.expect(containsString("Wrapped by: "
+ "java.lang.RuntimeException: Expected"));
this.loggingSystem.initialize(null, null, getLogFile(null, tmpDir()));
Matcher<String> expectedOutput = Matchers.allOf(
containsString("java.lang.RuntimeException: Expected"),
not(containsString("Wrapped by:")));
this.output.expect(expectedOutput);
this.logger.warn("Expected exception", new RuntimeException("Expected",
new RuntimeException("Cause")));
String fileContents = FileCopyUtils.copyToString(new FileReader(new File(
tmpDir() + "/spring.log")));
assertThat(fileContents, is(expectedOutput));
}
finally {
System.clearProperty("LOG_EXCEPTION_CONVERSION_WORD");
}
}
private static class TestLog4J2LoggingSystem extends Log4J2LoggingSystem {

@ -23,6 +23,8 @@ import java.util.logging.LogManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.impl.SLF4JLogFactory;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@ -44,6 +46,8 @@ import ch.qos.logback.classic.LoggerContext;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@ -241,21 +245,52 @@ public class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
@Test
public void exceptionsIncludeClassPackaging() throws Exception {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(this.initializationContext, null, null);
this.output.expect(containsString("[junit-"));
this.loggingSystem.initialize(this.initializationContext, null,
getLogFile(null, tmpDir()));
Matcher<String> expectedOutput = containsString("[junit-");
this.output.expect(expectedOutput);
this.logger.warn("Expected exception", new RuntimeException("Expected"));
String fileContents = FileCopyUtils.copyToString(new FileReader(new File(tmpDir()
+ "/spring.log")));
assertThat(fileContents, is(expectedOutput));
}
@Test
public void rootCauseIsLoggedFirst() throws Exception {
this.loggingSystem.beforeInitialize();
this.loggingSystem.initialize(this.initializationContext, null,
getLogFile(null, tmpDir()));
Matcher<String> expectedOutput = containsString("Wrapped by: "
+ "java.lang.RuntimeException: Expected");
this.output.expect(expectedOutput);
this.logger.warn("Expected exception", new RuntimeException("Expected",
new RuntimeException("Cause")));
String fileContents = FileCopyUtils.copyToString(new FileReader(new File(tmpDir()
+ "/spring.log")));
assertThat(fileContents, is(expectedOutput));
}
@Test
public void customExceptionConversionWord() throws Exception {
System.setProperty("LOG_EXCEPTION_CONVERSION_WORD", "%ex");
try {
this.loggingSystem.beforeInitialize();
this.logger.info("Hidden");
this.loggingSystem.initialize(this.initializationContext, null, null);
this.output.expect(containsString("Wrapped by: "
+ "java.lang.RuntimeException: Expected"));
this.loggingSystem.initialize(this.initializationContext, null,
getLogFile(null, tmpDir()));
Matcher<String> expectedOutput = Matchers.allOf(
containsString("java.lang.RuntimeException: Expected"),
not(containsString("Wrapped by:")));
this.output.expect(expectedOutput);
this.logger.warn("Expected exception", new RuntimeException("Expected",
new RuntimeException("Cause")));
String fileContents = FileCopyUtils.copyToString(new FileReader(new File(
tmpDir() + "/spring.log")));
assertThat(fileContents, is(expectedOutput));
}
finally {
System.clearProperty("LOG_EXCEPTION_CONVERSION_WORD");
}
}
private String getLineWithText(File file, String outputSearch) throws Exception {

Loading…
Cancel
Save