diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 4ff31707edf2..7e288e01beef 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -43,6 +43,8 @@ content into your application. Rather, pick only the properties that you need. logging.file= # Log file name. For instance, `myapp.log` logging.file.max-history= # Maximum of archive log files to keep. Only supported with the default logback setup. logging.file.max-size= # Maximum log file size. Only supported with the default logback setup. + logging.filters.console= # Comma-separated list of filter classes to apply to console appender. Only supported with the default logback setup. + logging.filters.file= # Comma-separated list of filter classes to apply to file appender. Only supported with the default logback setup. logging.level.*= # Log levels severity mapping. For instance, `logging.level.org.springframework=DEBUG` logging.path= # Location of the log file. For instance, `/var/log` logging.pattern.console= # Appender pattern for output to the console. Supported only with the default Logback setup. 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 914829072b1d..0a19c4182db5 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 @@ -25,6 +25,8 @@ import ch.qos.logback.core.Appender; import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.CoreConstants; +import ch.qos.logback.core.OutputStreamAppender; +import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; import ch.qos.logback.core.util.FileSize; @@ -120,6 +122,11 @@ private Appender consoleAppender(LogbackConfigurator config) { encoder.setCharset(StandardCharsets.UTF_8); config.start(encoder); appender.setEncoder(encoder); + String[] filterClasses = this.patterns.getProperty("logging.filters.console", + String[].class); + if (filterClasses != null) { + configureFilters(config, appender, filterClasses); + } config.appender("CONSOLE", appender); return appender; } @@ -135,6 +142,11 @@ private Appender fileAppender(LogbackConfigurator config, config.start(encoder); appender.setFile(logFile); setRollingPolicy(appender, config, logFile); + String[] filterClasses = this.patterns.getProperty("logging.filters.file", + String[].class); + if (filterClasses != null) { + configureFilters(config, appender, filterClasses); + } config.appender("FILE", appender); return appender; } @@ -166,4 +178,20 @@ private void setMaxFileSize( } } + @SuppressWarnings("unchecked") + private void configureFilters(LogbackConfigurator config, + OutputStreamAppender appender, String[] filterClasses) { + for (String filterClass : filterClasses) { + try { + Filter filter = (Filter) Class.forName(filterClass).newInstance(); + config.start(filter); + appender.addFilter(filter); + } + catch (Exception e) { + throw new IllegalStateException( + "Unable to configure " + filterClass + " logging filter"); + } + } + } + } 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 64710529020b..e62f74ed8c71 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 @@ -85,6 +85,18 @@ "sourceType": "org.springframework.boot.context.logging.LoggingApplicationListener", "defaultValue": 0 }, + { + "name": "logging.filter.console", + "type": "java.lang.String[]", + "description": "Comma-separated list of filter classes to apply to console appender. Only supported with the default logback setup.", + "sourceType": "org.springframework.boot.logging.LoggingApplicationListener" + }, + { + "name": "logging.filter.file", + "type": "java.lang.String[]", + "description": "Comma-separated list of filter classes to apply to file appender. Only supported with the default logback setup.", + "sourceType": "org.springframework.boot.logging.LoggingApplicationListener" + }, { "name": "logging.level", "type": "java.util.Map", 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 b8b7eff22636..c61883da9eb5 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 @@ -26,11 +26,14 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.classic.spi.LoggerContextListener; import ch.qos.logback.core.ConsoleAppender; import ch.qos.logback.core.CoreConstants; +import ch.qos.logback.core.filter.Filter; import ch.qos.logback.core.rolling.RollingFileAppender; import ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy; +import ch.qos.logback.core.spi.FilterReply; import org.apache.commons.logging.Log; import org.apache.commons.logging.impl.SLF4JLogFactory; import org.hamcrest.Matcher; @@ -487,6 +490,45 @@ public void testDateformatPatternProperty() { .containsPattern("\\d{4}-\\d{2}\\-\\d{2}T\\d{2}:\\d{2}:\\d{2}"); } + @Test + public void consoleFiltersProperty() throws Exception { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("logging.filters.console", HelloFilter.class.getName() + + "," + HiFilter.class.getName()); + LoggingInitializationContext loggingInitializationContext = + new LoggingInitializationContext(environment); + this.loggingSystem.initialize(loggingInitializationContext, null, null); + this.logger.info("Hello world"); + this.logger.info("Hi world"); + this.logger.info("Bye world"); + String output = this.output.toString().trim(); + assertThat(output).doesNotContain("Hello world"); + assertThat(output).doesNotContain("Hi world"); + assertThat(output).contains("Bye world"); + } + + @Test + public void fileFiltersProperty() throws Exception { + MockEnvironment environment = new MockEnvironment(); + environment.setProperty("logging.filters.file", HelloFilter.class.getName() + + "," + HiFilter.class.getName()); + LoggingInitializationContext loggingInitializationContext = + new LoggingInitializationContext(environment); + this.loggingSystem.initialize(loggingInitializationContext, null, + getLogFile(null, tmpDir())); + this.logger.info("Hello world"); + this.logger.info("Hi world"); + this.logger.info("Bye world"); + String output = this.output.toString().trim(); + File file = new File(tmpDir() + "/spring.log"); + assertThat(output).contains("Hello world"); + assertThat(output).contains("Hi world"); + assertThat(output).contains("Bye world"); + assertThat(getLineWithText(file, "Hello world")).isNull(); + assertThat(getLineWithText(file, "Hi world")).isNull(); + assertThat(getLineWithText(file, "Bye world")).isNotEmpty(); + } + private static Logger getRootLogger() { ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); LoggerContext context = (LoggerContext) factory; @@ -520,4 +562,24 @@ private String getLineWithText(String output, String outputSearch) { return null; } + static class HelloFilter extends Filter { + + @Override + public FilterReply decide(ILoggingEvent event) { + return event.getMessage().contains("Hello") + ? FilterReply.DENY : FilterReply.NEUTRAL; + } + + } + + static class HiFilter extends Filter { + + @Override + public FilterReply decide(ILoggingEvent event) { + return event.getMessage().contains("Hi") + ? FilterReply.DENY : FilterReply.NEUTRAL; + } + + } + }