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
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -1195,7 +1195,9 @@ These are:
|
||||
* `${PID}` the current process ID.
|
||||
* `${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 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.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 = 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, 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.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 = 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,
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user