Add properties for logging charsets

Add `logging.charset.console` and `logging.charset.file` properties
that can be used to configure charsets for Logback/Log4J2.

Closes gh-23827
pull/23886/head
Phillip Webb 4 years ago
parent e790828e19
commit 062bd90d87

@ -2204,10 +2204,18 @@ To help with the customization, some other properties are transferred from the S
| `LOG_DATEFORMAT_PATTERN` | `LOG_DATEFORMAT_PATTERN`
| Appender pattern for log date format. | Appender pattern for log date format.
| configprop:logging.charset.console[]
| `CONSOLE_LOG_CHARSET`
| The charset to use for console logging.
| configprop:logging.pattern.file[] | configprop:logging.pattern.file[]
| `FILE_LOG_PATTERN` | `FILE_LOG_PATTERN`
| The log pattern to use in a file (if `LOG_FILE` is enabled). | The log pattern to use in a file (if `LOG_FILE` is enabled).
| configprop:logging.charset.file[]
| `FILE_LOG_CHARSET`
| The charset to use for file logging (if `LOG_FILE` is enabled).
| configprop:logging.pattern.level[] | configprop:logging.pattern.level[]
| `LOG_LEVEL_PATTERN` | `LOG_LEVEL_PATTERN`
| The format to use when rendering the log level (default `%5p`). | The format to use when rendering the log level (default `%5p`).

@ -16,6 +16,9 @@
package org.springframework.boot.logging; package org.springframework.boot.logging;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.springframework.boot.system.ApplicationPid; import org.springframework.boot.system.ApplicationPid;
import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -61,11 +64,21 @@ public class LoggingSystemProperties {
*/ */
public static final String CONSOLE_LOG_PATTERN = "CONSOLE_LOG_PATTERN"; public static final String CONSOLE_LOG_PATTERN = "CONSOLE_LOG_PATTERN";
/**
* The name of the System property that contains the console log charset.
*/
public static final String CONSOLE_LOG_CHARSET = "CONSOLE_LOG_CHARSET";
/** /**
* The name of the System property that contains the file log pattern. * The name of the System property that contains the file log pattern.
*/ */
public static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN"; public static final String FILE_LOG_PATTERN = "FILE_LOG_PATTERN";
/**
* The name of the System property that contains the file log charset.
*/
public static final String FILE_LOG_CHARSET = "FILE_LOG_CHARSET";
/** /**
* The name of the System property that contains the rolled-over log file name * The name of the System property that contains the rolled-over log file name
* pattern. * pattern.
@ -128,6 +141,10 @@ public class LoggingSystemProperties {
this.environment = environment; this.environment = environment;
} }
protected Charset getDefaultCharset() {
return StandardCharsets.UTF_8;
}
public final void apply() { public final void apply() {
apply(null); apply(null);
} }
@ -141,8 +158,10 @@ public class LoggingSystemProperties {
setSystemProperty(resolver, EXCEPTION_CONVERSION_WORD, "logging.exception-conversion-word"); setSystemProperty(resolver, EXCEPTION_CONVERSION_WORD, "logging.exception-conversion-word");
setSystemProperty(PID_KEY, new ApplicationPid().toString()); setSystemProperty(PID_KEY, new ApplicationPid().toString());
setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "logging.pattern.console"); setSystemProperty(resolver, CONSOLE_LOG_PATTERN, "logging.pattern.console");
setSystemProperty(resolver, CONSOLE_LOG_CHARSET, "logging.charset.console", getDefaultCharset().name());
setSystemProperty(resolver, LOG_DATEFORMAT_PATTERN, "logging.pattern.dateformat"); setSystemProperty(resolver, LOG_DATEFORMAT_PATTERN, "logging.pattern.dateformat");
setSystemProperty(resolver, FILE_LOG_PATTERN, "logging.pattern.file"); setSystemProperty(resolver, FILE_LOG_PATTERN, "logging.pattern.file");
setSystemProperty(resolver, FILE_LOG_CHARSET, "logging.charset.file", getDefaultCharset().name());
setSystemProperty(resolver, LOG_LEVEL_PATTERN, "logging.pattern.level"); setSystemProperty(resolver, LOG_LEVEL_PATTERN, "logging.pattern.level");
applyDeprecated(resolver); applyDeprecated(resolver);
if (logFile != null) { if (logFile != null) {
@ -170,7 +189,14 @@ public class LoggingSystemProperties {
} }
protected final void setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName) { protected final void setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName) {
setSystemProperty(systemPropertyName, resolver.getProperty(propertyName)); setSystemProperty(resolver, systemPropertyName, propertyName, null);
}
protected final void setSystemProperty(PropertyResolver resolver, String systemPropertyName, String propertyName,
String defaultValue) {
String value = resolver.getProperty(propertyName);
value = (value != null) ? value : defaultValue;
setSystemProperty(systemPropertyName, value);
} }
protected final void setSystemProperty(String name, String value) { protected final void setSystemProperty(String name, String value) {

@ -16,6 +16,8 @@
package org.springframework.boot.logging.logback; package org.springframework.boot.logging.logback;
import java.nio.charset.Charset;
import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.ILoggingEvent;
@ -87,6 +89,7 @@ class DefaultLogbackConfiguration {
ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>(); ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
PatternLayoutEncoder encoder = new PatternLayoutEncoder(); PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setPattern(resolve(config, "${CONSOLE_LOG_PATTERN}")); encoder.setPattern(resolve(config, "${CONSOLE_LOG_PATTERN}"));
encoder.setCharset(resolveCharset(config, "${CONSOLE_LOG_CHARSET}"));
config.start(encoder); config.start(encoder);
appender.setEncoder(encoder); appender.setEncoder(encoder);
config.appender("CONSOLE", appender); config.appender("CONSOLE", appender);
@ -97,6 +100,7 @@ class DefaultLogbackConfiguration {
RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>(); RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
PatternLayoutEncoder encoder = new PatternLayoutEncoder(); PatternLayoutEncoder encoder = new PatternLayoutEncoder();
encoder.setPattern(resolve(config, "${FILE_LOG_PATTERN}")); encoder.setPattern(resolve(config, "${FILE_LOG_PATTERN}"));
encoder.setCharset(resolveCharset(config, "${FILE_LOG_CHARSET}"));
appender.setEncoder(encoder); appender.setEncoder(encoder);
config.start(encoder); config.start(encoder);
appender.setFile(logFile); appender.setFile(logFile);
@ -133,6 +137,10 @@ class DefaultLogbackConfiguration {
return FileSize.valueOf(resolve(config, val)); return FileSize.valueOf(resolve(config, val));
} }
private Charset resolveCharset(LogbackConfigurator config, String val) {
return Charset.forName(resolve(config, val));
}
private String resolve(LogbackConfigurator config, String val) { private String resolve(LogbackConfigurator config, String val) {
return OptionHelper.substVars(val, config.getContext()); return OptionHelper.substVars(val, config.getContext());
} }

@ -16,6 +16,8 @@
package org.springframework.boot.logging.logback; package org.springframework.boot.logging.logback;
import java.nio.charset.Charset;
import ch.qos.logback.core.util.FileSize; import ch.qos.logback.core.util.FileSize;
import org.springframework.boot.logging.LogFile; import org.springframework.boot.logging.LogFile;
@ -64,6 +66,11 @@ public class LogbackLoggingSystemProperties extends LoggingSystemProperties {
super(environment); super(environment);
} }
@Override
protected Charset getDefaultCharset() {
return Charset.defaultCharset();
}
@Override @Override
protected void apply(LogFile logFile, PropertyResolver resolver) { protected void apply(LogFile logFile, PropertyResolver resolver) {
super.apply(logFile, resolver); super.apply(logFile, resolver);

@ -19,6 +19,16 @@
"description": "Location of the logging configuration file. For instance, `classpath:logback.xml` for Logback.", "description": "Location of the logging configuration file. For instance, `classpath:logback.xml` for Logback.",
"sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener" "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener"
}, },
{
"name": "logging.charset.console",
"type": "java.nio.Charset",
"description": "The charset to use for console output"
},
{
"name": "logging.charset.file",
"type": "java.nio.Charset",
"description": "The charset to use for file output"
},
{ {
"name": "logging.exception-conversion-word", "name": "logging.exception-conversion-word",
"type": "java.lang.String", "type": "java.lang.String",

@ -9,12 +9,10 @@
</Properties> </Properties>
<Appenders> <Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true"> <Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" /> <PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" charset="${sys:CONSOLE_LOG_CHARSET}" />
</Console> </Console>
<RollingFile name="File" fileName="${sys:LOG_FILE}" filePattern="${sys:LOG_PATH}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz"> <RollingFile name="File" fileName="${sys:LOG_FILE}" filePattern="${sys:LOG_PATH}/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
<PatternLayout> <PatternLayout pattern="${sys:FILE_LOG_PATTERN}" charset="${sys:FILE_LOG_CHARSET}"/>
<Pattern>${sys:FILE_LOG_PATTERN}</Pattern>
</PatternLayout>
<Policies> <Policies>
<SizeBasedTriggeringPolicy size="10 MB" /> <SizeBasedTriggeringPolicy size="10 MB" />
</Policies> </Policies>

@ -9,7 +9,7 @@
</Properties> </Properties>
<Appenders> <Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true"> <Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" /> <PatternLayout pattern="${sys:CONSOLE_LOG_PATTERN}" charset="${sys:CONSOLE_LOG_CHARSET}"/>
</Console> </Console>
</Appenders> </Appenders>
<Loggers> <Loggers>

@ -9,6 +9,7 @@ initialization performed by Boot
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder> <encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern> <pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>${CONSOLE_LOG_CHARSET}</charset>
</encoder> </encoder>
</appender> </appender>
</included> </included>

@ -6,10 +6,10 @@ initialization performed by Boot
--> -->
<included> <included>
<appender name="FILE" <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder> <encoder>
<pattern>${FILE_LOG_PATTERN}</pattern> <pattern>${FILE_LOG_PATTERN}</pattern>
<charset>${FILE_LOG_CHARSET}</charset>
</encoder> </encoder>
<file>${LOG_FILE}</file> <file>${LOG_FILE}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">

@ -43,6 +43,8 @@ class LoggingSystemPropertiesTests {
@BeforeEach @BeforeEach
void captureSystemPropertyNames() { void captureSystemPropertyNames() {
System.getProperties().remove(LoggingSystemProperties.CONSOLE_LOG_CHARSET);
System.getProperties().remove(LoggingSystemProperties.FILE_LOG_CHARSET);
this.systemPropertyNames = new HashSet<>(System.getProperties().keySet()); this.systemPropertyNames = new HashSet<>(System.getProperties().keySet());
} }
@ -64,6 +66,19 @@ class LoggingSystemPropertiesTests {
assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_PATTERN)).isEqualTo("console pattern"); assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_PATTERN)).isEqualTo("console pattern");
} }
@Test
void consoleCharsetWhenNoPropertyUsesUtf8() {
new LoggingSystemProperties(new MockEnvironment()).apply(null);
assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_CHARSET)).isEqualTo("UTF-8");
}
@Test
void consoleCharsetIsSet() {
new LoggingSystemProperties(new MockEnvironment().withProperty("logging.charset.console", "UTF-16"))
.apply(null);
assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_CHARSET)).isEqualTo("UTF-16");
}
@Test @Test
void fileLogPatternIsSet() { void fileLogPatternIsSet() {
new LoggingSystemProperties(new MockEnvironment().withProperty("logging.pattern.file", "file pattern")) new LoggingSystemProperties(new MockEnvironment().withProperty("logging.pattern.file", "file pattern"))
@ -71,6 +86,18 @@ class LoggingSystemPropertiesTests {
assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_PATTERN)).isEqualTo("file pattern"); assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_PATTERN)).isEqualTo("file pattern");
} }
@Test
void fileCharsetWhenNoPropertyUsesUtf8() {
new LoggingSystemProperties(new MockEnvironment()).apply(null);
assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_CHARSET)).isEqualTo("UTF-8");
}
@Test
void fileCharsetIsSet() {
new LoggingSystemProperties(new MockEnvironment().withProperty("logging.charset.file", "UTF-16")).apply(null);
assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_CHARSET)).isEqualTo("UTF-16");
}
@Test @Test
void consoleLogPatternCanReferencePid() { void consoleLogPatternCanReferencePid() {
new LoggingSystemProperties(environment("logging.pattern.console", "${PID:unknown}")).apply(null); new LoggingSystemProperties(environment("logging.pattern.console", "${PID:unknown}")).apply(null);

@ -16,6 +16,7 @@
package org.springframework.boot.logging.logback; package org.springframework.boot.logging.logback;
import java.nio.charset.Charset;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -43,6 +44,8 @@ class LogbackLoggingSystemPropertiesTests {
@BeforeEach @BeforeEach
void captureSystemPropertyNames() { void captureSystemPropertyNames() {
System.getProperties().remove(LoggingSystemProperties.CONSOLE_LOG_CHARSET);
System.getProperties().remove(LoggingSystemProperties.FILE_LOG_CHARSET);
this.systemPropertyNames = new HashSet<>(System.getProperties().keySet()); this.systemPropertyNames = new HashSet<>(System.getProperties().keySet());
this.environment = new MockEnvironment(); this.environment = new MockEnvironment();
this.environment this.environment
@ -94,4 +97,18 @@ class LogbackLoggingSystemPropertiesTests {
.containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_MAX_HISTORY, "mh"); .containsEntry(LogbackLoggingSystemProperties.ROLLINGPOLICY_MAX_HISTORY, "mh");
} }
@Test
void consoleCharsetWhenNoPropertyUsesDefault() {
new LoggingSystemProperties(new MockEnvironment()).apply(null);
assertThat(System.getProperty(LoggingSystemProperties.CONSOLE_LOG_CHARSET))
.isEqualTo(Charset.defaultCharset().name());
}
@Test
void fileCharsetWhenNoPropertyUsesDefault() {
new LoggingSystemProperties(new MockEnvironment()).apply(null);
assertThat(System.getProperty(LoggingSystemProperties.FILE_LOG_CHARSET))
.isEqualTo(Charset.defaultCharset().name());
}
} }

@ -17,6 +17,7 @@
package org.springframework.boot.logging.logback; package org.springframework.boot.logging.logback;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
@ -30,6 +31,7 @@ import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.LoggerContextListener; import ch.qos.logback.classic.spi.LoggerContextListener;
import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.encoder.LayoutWrappingEncoder;
import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.RollingFileAppender;
import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -564,6 +566,18 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests {
assertThat(getRollingPolicy().getFileNamePattern()).isEqualTo(rollingFile); assertThat(getRollingPolicy().getFileNamePattern()).isEqualTo(rollingFile);
} }
@Test
void customCharset() {
this.environment.setProperty("logging.charset.console", "UTF-16");
LoggingInitializationContext loggingInitializationContext = new LoggingInitializationContext(this.environment);
File file = new File(tmpDir(), "logback-test.log");
LogFile logFile = getLogFile(file.getPath(), null);
initialize(loggingInitializationContext, null, logFile);
this.logger.info("Hello world");
LayoutWrappingEncoder<?> encoder = (LayoutWrappingEncoder<?>) getConsoleAppender().getEncoder();
assertThat(encoder.getCharset()).isEqualTo(StandardCharsets.UTF_16);
}
private void initialize(LoggingInitializationContext context, String configLocation, LogFile logFile) { private void initialize(LoggingInitializationContext context, String configLocation, LogFile logFile) {
this.loggingSystem.getSystemProperties((ConfigurableEnvironment) context.getEnvironment()).apply(logFile); this.loggingSystem.getSystemProperties((ConfigurableEnvironment) context.getEnvironment()).apply(logFile);
this.loggingSystem.initialize(context, configLocation, logFile); this.loggingSystem.initialize(context, configLocation, logFile);

Loading…
Cancel
Save