diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc index 1e0ea3c90b..a2433e21cd 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc @@ -2204,10 +2204,18 @@ To help with the customization, some other properties are transferred from the S | `LOG_DATEFORMAT_PATTERN` | Appender pattern for log date format. +| configprop:logging.charset.console[] +| `CONSOLE_LOG_CHARSET` +| The charset to use for console logging. + | configprop:logging.pattern.file[] | `FILE_LOG_PATTERN` | 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[] | `LOG_LEVEL_PATTERN` | The format to use when rendering the log level (default `%5p`). diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java index 39d8a00c54..89021b3efa 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/LoggingSystemProperties.java @@ -16,6 +16,9 @@ package org.springframework.boot.logging; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + import org.springframework.boot.system.ApplicationPid; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; @@ -61,11 +64,21 @@ public class LoggingSystemProperties { */ 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. */ 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 * pattern. @@ -128,6 +141,10 @@ public class LoggingSystemProperties { this.environment = environment; } + protected Charset getDefaultCharset() { + return StandardCharsets.UTF_8; + } + public final void apply() { apply(null); } @@ -141,8 +158,10 @@ public class LoggingSystemProperties { setSystemProperty(resolver, EXCEPTION_CONVERSION_WORD, "logging.exception-conversion-word"); setSystemProperty(PID_KEY, new ApplicationPid().toString()); 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, FILE_LOG_PATTERN, "logging.pattern.file"); + setSystemProperty(resolver, FILE_LOG_CHARSET, "logging.charset.file", getDefaultCharset().name()); setSystemProperty(resolver, LOG_LEVEL_PATTERN, "logging.pattern.level"); applyDeprecated(resolver); if (logFile != null) { @@ -170,7 +189,14 @@ public class LoggingSystemProperties { } 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) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java index 49aa058f7e..b1db1dc9b9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.logging.logback; +import java.nio.charset.Charset; + import ch.qos.logback.classic.Level; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; @@ -87,6 +89,7 @@ class DefaultLogbackConfiguration { ConsoleAppender appender = new ConsoleAppender<>(); PatternLayoutEncoder encoder = new PatternLayoutEncoder(); encoder.setPattern(resolve(config, "${CONSOLE_LOG_PATTERN}")); + encoder.setCharset(resolveCharset(config, "${CONSOLE_LOG_CHARSET}")); config.start(encoder); appender.setEncoder(encoder); config.appender("CONSOLE", appender); @@ -97,6 +100,7 @@ class DefaultLogbackConfiguration { RollingFileAppender appender = new RollingFileAppender<>(); PatternLayoutEncoder encoder = new PatternLayoutEncoder(); encoder.setPattern(resolve(config, "${FILE_LOG_PATTERN}")); + encoder.setCharset(resolveCharset(config, "${FILE_LOG_CHARSET}")); appender.setEncoder(encoder); config.start(encoder); appender.setFile(logFile); @@ -133,6 +137,10 @@ class DefaultLogbackConfiguration { 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) { return OptionHelper.substVars(val, config.getContext()); } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java index 09ff574d8b..9982bcfb62 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystemProperties.java @@ -16,6 +16,8 @@ package org.springframework.boot.logging.logback; +import java.nio.charset.Charset; + import ch.qos.logback.core.util.FileSize; import org.springframework.boot.logging.LogFile; @@ -64,6 +66,11 @@ public class LogbackLoggingSystemProperties extends LoggingSystemProperties { super(environment); } + @Override + protected Charset getDefaultCharset() { + return Charset.defaultCharset(); + } + @Override protected void apply(LogFile logFile, PropertyResolver resolver) { super.apply(logFile, resolver); diff --git a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 254fe80e61..65fb7e2404 100644 --- a/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-boot-project/spring-boot/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -19,6 +19,16 @@ "description": "Location of the logging configuration file. For instance, `classpath:logback.xml` for Logback.", "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", "type": "java.lang.String", diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml index c1110066d3..07c58f7de1 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2-file.xml @@ -9,12 +9,10 @@ - + - - ${sys:FILE_LOG_PATTERN} - + diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml index 0a0fdbd4ce..2ad69f3ae6 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/log4j2/log4j2.xml @@ -9,7 +9,7 @@ - + diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/console-appender.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/console-appender.xml index 92d0da03f8..d172e4a9ef 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/console-appender.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/console-appender.xml @@ -9,6 +9,7 @@ initialization performed by Boot ${CONSOLE_LOG_PATTERN} + ${CONSOLE_LOG_CHARSET} diff --git a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml index fadd1e4ca0..2365a4df04 100644 --- a/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml +++ b/spring-boot-project/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml @@ -6,10 +6,10 @@ initialization performed by Boot --> - + ${FILE_LOG_PATTERN} + ${FILE_LOG_CHARSET} ${LOG_FILE} diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java index e82165ae8d..7ad1de14d6 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/LoggingSystemPropertiesTests.java @@ -43,6 +43,8 @@ class LoggingSystemPropertiesTests { @BeforeEach void captureSystemPropertyNames() { + System.getProperties().remove(LoggingSystemProperties.CONSOLE_LOG_CHARSET); + System.getProperties().remove(LoggingSystemProperties.FILE_LOG_CHARSET); this.systemPropertyNames = new HashSet<>(System.getProperties().keySet()); } @@ -64,6 +66,19 @@ class LoggingSystemPropertiesTests { 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 void fileLogPatternIsSet() { 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"); } + @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 void consoleLogPatternCanReferencePid() { new LoggingSystemProperties(environment("logging.pattern.console", "${PID:unknown}")).apply(null); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemPropertiesTests.java index 7e8ddda1d0..5e4f0596d5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemPropertiesTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.logging.logback; +import java.nio.charset.Charset; import java.util.HashSet; import java.util.Set; @@ -43,6 +44,8 @@ class LogbackLoggingSystemPropertiesTests { @BeforeEach void captureSystemPropertyNames() { + System.getProperties().remove(LoggingSystemProperties.CONSOLE_LOG_CHARSET); + System.getProperties().remove(LoggingSystemProperties.FILE_LOG_CHARSET); this.systemPropertyNames = new HashSet<>(System.getProperties().keySet()); this.environment = new MockEnvironment(); this.environment @@ -94,4 +97,18 @@ class LogbackLoggingSystemPropertiesTests { .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()); + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java index 64e8170081..174c07fed3 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/logging/logback/LogbackLoggingSystemTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.logging.logback; import java.io.File; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.EnumSet; 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.spi.LoggerContextListener; 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.SizeAndTimeBasedRollingPolicy; import org.junit.jupiter.api.AfterEach; @@ -564,6 +566,18 @@ class LogbackLoggingSystemTests extends AbstractLoggingSystemTests { 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) { this.loggingSystem.getSystemProperties((ConfigurableEnvironment) context.getEnvironment()).apply(logFile); this.loggingSystem.initialize(context, configLocation, logFile);