From 931add4a7daffe2b255f5774af44ec1a12877d91 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Thu, 30 Oct 2014 13:29:39 -0700 Subject: [PATCH] Programmatically configure logback defaults Update LogbackLoggingSystem to programmatically configure logback defaults rather than parsing XML. This change shaves about 100ms off the start up time. Fixes gh-1796 --- .../logback/DefaultLogbackConfiguration.java | 135 ++++++++++++++++++ .../logback/LevelRemappingAppender.java | 14 ++ .../logging/logback/LogbackConfigurator.java | 114 +++++++++++++++ .../logging/logback/LogbackLoggingSystem.java | 39 ++--- .../boot/logging/logback/base.xml | 6 + .../boot/logging/logback/console-appender.xml | 6 + .../boot/logging/logback/file-appender.xml | 6 + .../boot/logging/logback/logback-file.xml | 12 -- .../boot/logging/logback/logback.xml | 8 -- 9 files changed, 302 insertions(+), 38 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackConfigurator.java delete mode 100644 spring-boot/src/main/resources/org/springframework/boot/logging/logback/logback-file.xml delete mode 100644 spring-boot/src/main/resources/org/springframework/boot/logging/logback/logback.xml diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java b/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java new file mode 100644 index 00000000000..7f4f31f3c50 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/logging/logback/DefaultLogbackConfiguration.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.logging.logback; + +import java.nio.charset.Charset; + +import org.springframework.util.StringUtils; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.ConsoleAppender; +import ch.qos.logback.core.rolling.FixedWindowRollingPolicy; +import ch.qos.logback.core.rolling.RollingFileAppender; +import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy; +import ch.qos.logback.core.util.OptionHelper; + +/** + * Default logback configuration used by Spring Boot. Uses {@link LogbackConfigurator} to + * improve startup time. See also the {@code base.xml}, {@code console-appender.xml} and + * {@code file-appender.xml} files provided for classic {@code logback.xml} use. + * + * @author Phillip Webb + * @since 1.1.2 + */ +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{14}]){faint} %clr(%-40.40logger{39}){cyan} " + + "%clr(:){faint} %m%n%wex"; + + private static final String FILE_LOG_PATTERN = "%d{yyyy-MM-dd HH:mm:ss.SSS} %5p " + + "${PID:- } [%t] --- %-40.40logger{39} : %m%n%wex"; + + private static final Charset UTF8 = Charset.forName("UTF-8"); + + private final String logFile; + + public DefaultLogbackConfiguration(String logFile) { + this.logFile = logFile; + } + + @SuppressWarnings("unchecked") + public void apply(LogbackConfigurator config) { + synchronized (config.getConfigurationLock()) { + base(config); + Appender consoleAppender = consoleAppender(config); + if (StringUtils.hasLength(this.logFile)) { + Appender fileAppender = fileAppender(config, this.logFile); + config.root(Level.INFO, consoleAppender, fileAppender); + } + else { + config.root(Level.INFO, consoleAppender); + } + } + } + + private void base(LogbackConfigurator config) { + config.conversionRule("clr", ColorConverter.class); + config.conversionRule("wex", WhitespaceThrowableProxyConverter.class); + LevelRemappingAppender debugRemapAppender = new LevelRemappingAppender( + "org.springframework.boot"); + config.start(debugRemapAppender); + config.appender("DEBUG_LEVEL_REMAPPER", debugRemapAppender); + config.logger("", Level.ERROR); + config.logger("org.apache.catalina.startup.DigesterFactory", Level.ERROR); + config.logger("org.apache.catalina.util.LifecycleBase", Level.ERROR); + config.logger("org.apache.coyote.http11.Http11NioProtocol", Level.WARN); + config.logger("org.apache.sshd.common.util.SecurityUtils", Level.WARN); + config.logger("org.apache.tomcat.util.net.NioSelectorPool", Level.WARN); + config.logger("org.crsh.plugin", Level.WARN); + config.logger("org.crsh.ssh", Level.WARN); + config.logger("org.eclipse.jetty.util.component.AbstractLifeCycle", Level.ERROR); + config.logger("org.hibernate.validator.internal.util.Version", Level.WARN); + config.logger("org.springframework.boot.actuate.autoconfigure." + + "CrshAutoConfiguration", Level.WARN); + config.logger("org.springframework.boot.actuate.endpoint.jmx", null, false, + debugRemapAppender); + config.logger("org.thymeleaf", null, false, debugRemapAppender); + } + + private Appender consoleAppender(LogbackConfigurator config) { + ConsoleAppender appender = new ConsoleAppender(); + PatternLayoutEncoder encoder = new PatternLayoutEncoder(); + encoder.setPattern(OptionHelper.substVars(CONSOLE_LOG_PATTERN, + config.getContext())); + encoder.setCharset(UTF8); + config.start(encoder); + appender.setEncoder(encoder); + config.appender("CONSOLE", appender); + return appender; + } + + private Appender fileAppender(LogbackConfigurator config, + String logFile) { + RollingFileAppender appender = new RollingFileAppender(); + PatternLayoutEncoder encoder = new PatternLayoutEncoder(); + encoder.setPattern(FILE_LOG_PATTERN); + appender.setEncoder(encoder); + config.start(encoder); + + appender.setFile(logFile); + + FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy(); + rollingPolicy.setFileNamePattern(logFile + ".%i"); + appender.setRollingPolicy(rollingPolicy); + rollingPolicy.setParent(appender); + config.start(rollingPolicy); + + SizeBasedTriggeringPolicy triggeringPolicy = new SizeBasedTriggeringPolicy(); + triggeringPolicy.setMaxFileSize("10MB"); + appender.setTriggeringPolicy(triggeringPolicy); + config.start(triggeringPolicy); + + config.appender("FILE", appender); + return appender; + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/logback/LevelRemappingAppender.java b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LevelRemappingAppender.java index 179307a4cee..122fad4215b 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/logback/LevelRemappingAppender.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LevelRemappingAppender.java @@ -50,6 +50,20 @@ public class LevelRemappingAppender extends AppenderBase { private Map remapLevels = DEFAULT_REMAPS; + /** + * Create a new {@link LevelRemappingAppender}. + */ + public LevelRemappingAppender() { + } + + /** + * Create a new {@link LevelRemappingAppender} with a specific destination logger. + * @param destinationLogger the destination logger + */ + public LevelRemappingAppender(String destinationLogger) { + this.destinationLogger = destinationLogger; + } + @Override protected void append(ILoggingEvent event) { AppendableLogger logger = getLogger(this.destinationLogger); diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackConfigurator.java b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackConfigurator.java new file mode 100644 index 00000000000..52378fa450f --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackConfigurator.java @@ -0,0 +1,114 @@ +/* + * Copyright 2012-2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.logging.logback; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.util.Assert; + +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.core.Appender; +import ch.qos.logback.core.CoreConstants; +import ch.qos.logback.core.pattern.Converter; +import ch.qos.logback.core.spi.ContextAware; +import ch.qos.logback.core.spi.LifeCycle; +import ch.qos.logback.core.spi.PropertyContainer; + +/** + * Allows programmatic configuration of logback which is usually faster than parsing XML. + * + * @author Phillip Webb + * @since 1.2.0 + */ +class LogbackConfigurator { + + private LoggerContext context; + + public LogbackConfigurator(LoggerContext context) { + Assert.notNull(context, "Context must not be null"); + this.context = context; + } + + public PropertyContainer getContext() { + return this.context; + } + + public Object getConfigurationLock() { + return this.context.getConfigurationLock(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public void conversionRule(String conversionWord, + Class converterClass) { + Assert.hasLength(conversionWord, "Conversion word must not be empty"); + Assert.notNull(converterClass, "Converter class must not be null"); + Map registry = (Map) this.context + .getObject(CoreConstants.PATTERN_RULE_REGISTRY); + if (registry == null) { + registry = new HashMap(); + this.context.putObject(CoreConstants.PATTERN_RULE_REGISTRY, registry); + } + registry.put(conversionWord, converterClass.getName()); + } + + public void appender(String name, Appender appender) { + appender.setName(name); + start(appender); + } + + public void logger(String name, Level level) { + logger(name, level, true); + } + + public void logger(String name, Level level, boolean additive) { + logger(name, level, additive, null); + } + + public void logger(String name, Level level, boolean additive, + Appender appender) { + Logger logger = this.context.getLogger(name); + if (level != null) { + logger.setLevel(level); + } + logger.setAdditive(additive); + if (appender != null) { + logger.addAppender(appender); + } + } + + public void root(Level level, Appender... appenders) { + Logger logger = this.context.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + if (level != null) { + logger.setLevel(level); + } + for (Appender appender : appenders) { + logger.addAppender(appender); + } + } + + public void start(LifeCycle lifeCycle) { + if (lifeCycle instanceof ContextAware) { + ((ContextAware) lifeCycle).setContext(this.context); + } + lifeCycle.start(); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java index fc48dec5578..68545939ece 100644 --- a/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java +++ b/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java @@ -94,12 +94,11 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { @Override protected void loadDefaults(String logFile) { - if (StringUtils.hasLength(logFile)) { - loadConfiguration(getPackagedConfigFile("logback-file.xml"), logFile); - } - else { - loadConfiguration(getPackagedConfigFile("logback.xml"), logFile); - } + LoggerContext context = getLoggerContext(); + context.stop(); + context.reset(); + LogbackConfigurator configurator = new LogbackConfigurator(context); + new DefaultLogbackConfiguration(logFile).apply(configurator); } @Override @@ -108,18 +107,7 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { if (StringUtils.hasLength(logFile)) { System.setProperty("LOG_FILE", logFile); } - ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); - Assert.isInstanceOf( - LoggerContext.class, - factory, - String.format( - "LoggerFactory is not a Logback LoggerContext but Logback is on " - + "the classpath. Either remove Logback or the competing " - + "implementation (%s loaded from %s).", - factory.getClass(), factory.getClass().getProtectionDomain() - .getCodeSource().getLocation())); - - LoggerContext context = (LoggerContext) factory; + LoggerContext context = getLoggerContext(); context.stop(); context.reset(); try { @@ -137,6 +125,21 @@ public class LogbackLoggingSystem extends Slf4JLoggingSystem { getLogger(loggerName).setLevel(LEVELS.get(level)); } + private LoggerContext getLoggerContext() { + ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); + Assert.isInstanceOf( + LoggerContext.class, + factory, + String.format( + "LoggerFactory is not a Logback LoggerContext but Logback is on " + + "the classpath. Either remove Logback or the competing " + + "implementation (%s loaded from %s).", + factory.getClass(), factory.getClass().getProtectionDomain() + .getCodeSource().getLocation())); + + return (LoggerContext) factory; + } + private ch.qos.logback.classic.Logger getLogger(String name) { ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); return (ch.qos.logback.classic.Logger) factory.getLogger(StringUtils diff --git a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/base.xml b/spring-boot/src/main/resources/org/springframework/boot/logging/logback/base.xml index 71d33621a61..047abff088e 100644 --- a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/base.xml +++ b/spring-boot/src/main/resources/org/springframework/boot/logging/logback/base.xml @@ -1,4 +1,10 @@ + + + diff --git a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/console-appender.xml b/spring-boot/src/main/resources/org/springframework/boot/logging/logback/console-appender.xml index a08abd194d2..6e84f416053 100644 --- a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/console-appender.xml +++ b/spring-boot/src/main/resources/org/springframework/boot/logging/logback/console-appender.xml @@ -1,4 +1,10 @@ + + + diff --git a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml b/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml index f6dd7835643..034cfa562d1 100644 --- a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml +++ b/spring-boot/src/main/resources/org/springframework/boot/logging/logback/file-appender.xml @@ -1,4 +1,10 @@ + + + diff --git a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/logback-file.xml b/spring-boot/src/main/resources/org/springframework/boot/logging/logback/logback-file.xml deleted file mode 100644 index 0d5c8a3c428..00000000000 --- a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/logback-file.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/logback.xml b/spring-boot/src/main/resources/org/springframework/boot/logging/logback/logback.xml deleted file mode 100644 index 5b372d2331b..00000000000 --- a/spring-boot/src/main/resources/org/springframework/boot/logging/logback/logback.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - -