001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.logging.logback; 018 019import java.net.URL; 020import java.security.CodeSource; 021import java.security.ProtectionDomain; 022import java.util.ArrayList; 023import java.util.List; 024import java.util.Set; 025import java.util.logging.Handler; 026import java.util.logging.LogManager; 027 028import ch.qos.logback.classic.Level; 029import ch.qos.logback.classic.LoggerContext; 030import ch.qos.logback.classic.joran.JoranConfigurator; 031import ch.qos.logback.classic.jul.LevelChangePropagator; 032import ch.qos.logback.classic.turbo.TurboFilter; 033import ch.qos.logback.classic.util.ContextInitializer; 034import ch.qos.logback.core.joran.spi.JoranException; 035import ch.qos.logback.core.spi.FilterReply; 036import ch.qos.logback.core.status.Status; 037import org.slf4j.ILoggerFactory; 038import org.slf4j.Logger; 039import org.slf4j.Marker; 040import org.slf4j.bridge.SLF4JBridgeHandler; 041import org.slf4j.impl.StaticLoggerBinder; 042 043import org.springframework.boot.logging.LogFile; 044import org.springframework.boot.logging.LogLevel; 045import org.springframework.boot.logging.LoggerConfiguration; 046import org.springframework.boot.logging.LoggingInitializationContext; 047import org.springframework.boot.logging.LoggingSystem; 048import org.springframework.boot.logging.LoggingSystemProperties; 049import org.springframework.boot.logging.Slf4JLoggingSystem; 050import org.springframework.core.env.Environment; 051import org.springframework.util.Assert; 052import org.springframework.util.ResourceUtils; 053import org.springframework.util.StringUtils; 054 055/** 056 * {@link LoggingSystem} for <a href="http://logback.qos.ch">logback</a>. 057 * 058 * @author Phillip Webb 059 * @author Dave Syer 060 * @author Andy Wilkinson 061 * @author Ben Hale 062 */ 063public class LogbackLoggingSystem extends Slf4JLoggingSystem { 064 065 private static final String CONFIGURATION_FILE_PROPERTY = "logback.configurationFile"; 066 067 private static final LogLevels<Level> LEVELS = new LogLevels<>(); 068 069 static { 070 LEVELS.map(LogLevel.TRACE, Level.TRACE); 071 LEVELS.map(LogLevel.TRACE, Level.ALL); 072 LEVELS.map(LogLevel.DEBUG, Level.DEBUG); 073 LEVELS.map(LogLevel.INFO, Level.INFO); 074 LEVELS.map(LogLevel.WARN, Level.WARN); 075 LEVELS.map(LogLevel.ERROR, Level.ERROR); 076 LEVELS.map(LogLevel.FATAL, Level.ERROR); 077 LEVELS.map(LogLevel.OFF, Level.OFF); 078 } 079 080 private static final TurboFilter FILTER = new TurboFilter() { 081 082 @Override 083 public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger, 084 Level level, String format, Object[] params, Throwable t) { 085 return FilterReply.DENY; 086 } 087 088 }; 089 090 public LogbackLoggingSystem(ClassLoader classLoader) { 091 super(classLoader); 092 } 093 094 @Override 095 protected String[] getStandardConfigLocations() { 096 return new String[] { "logback-test.groovy", "logback-test.xml", "logback.groovy", 097 "logback.xml" }; 098 } 099 100 @Override 101 public void beforeInitialize() { 102 LoggerContext loggerContext = getLoggerContext(); 103 if (isAlreadyInitialized(loggerContext)) { 104 return; 105 } 106 super.beforeInitialize(); 107 loggerContext.getTurboFilterList().add(FILTER); 108 } 109 110 @Override 111 public void initialize(LoggingInitializationContext initializationContext, 112 String configLocation, LogFile logFile) { 113 LoggerContext loggerContext = getLoggerContext(); 114 if (isAlreadyInitialized(loggerContext)) { 115 return; 116 } 117 super.initialize(initializationContext, configLocation, logFile); 118 loggerContext.getTurboFilterList().remove(FILTER); 119 markAsInitialized(loggerContext); 120 if (StringUtils.hasText(System.getProperty(CONFIGURATION_FILE_PROPERTY))) { 121 getLogger(LogbackLoggingSystem.class.getName()).warn( 122 "Ignoring '" + CONFIGURATION_FILE_PROPERTY + "' system property. " 123 + "Please use 'logging.config' instead."); 124 } 125 } 126 127 @Override 128 protected void loadDefaults(LoggingInitializationContext initializationContext, 129 LogFile logFile) { 130 LoggerContext context = getLoggerContext(); 131 stopAndReset(context); 132 LogbackConfigurator configurator = new LogbackConfigurator(context); 133 Environment environment = initializationContext.getEnvironment(); 134 context.putProperty(LoggingSystemProperties.LOG_LEVEL_PATTERN, 135 environment.resolvePlaceholders( 136 "${logging.pattern.level:${LOG_LEVEL_PATTERN:%5p}}")); 137 context.putProperty(LoggingSystemProperties.LOG_DATEFORMAT_PATTERN, 138 environment.resolvePlaceholders( 139 "${logging.pattern.dateformat:${LOG_DATEFORMAT_PATTERN:yyyy-MM-dd HH:mm:ss.SSS}}")); 140 new DefaultLogbackConfiguration(initializationContext, logFile) 141 .apply(configurator); 142 context.setPackagingDataEnabled(true); 143 } 144 145 @Override 146 protected void loadConfiguration(LoggingInitializationContext initializationContext, 147 String location, LogFile logFile) { 148 super.loadConfiguration(initializationContext, location, logFile); 149 LoggerContext loggerContext = getLoggerContext(); 150 stopAndReset(loggerContext); 151 try { 152 configureByResourceUrl(initializationContext, loggerContext, 153 ResourceUtils.getURL(location)); 154 } 155 catch (Exception ex) { 156 throw new IllegalStateException( 157 "Could not initialize Logback logging from " + location, ex); 158 } 159 List<Status> statuses = loggerContext.getStatusManager().getCopyOfStatusList(); 160 StringBuilder errors = new StringBuilder(); 161 for (Status status : statuses) { 162 if (status.getLevel() == Status.ERROR) { 163 errors.append((errors.length() > 0) ? String.format("%n") : ""); 164 errors.append(status.toString()); 165 } 166 } 167 if (errors.length() > 0) { 168 throw new IllegalStateException( 169 String.format("Logback configuration error detected: %n%s", errors)); 170 } 171 } 172 173 private void configureByResourceUrl( 174 LoggingInitializationContext initializationContext, 175 LoggerContext loggerContext, URL url) throws JoranException { 176 if (url.toString().endsWith("xml")) { 177 JoranConfigurator configurator = new SpringBootJoranConfigurator( 178 initializationContext); 179 configurator.setContext(loggerContext); 180 configurator.doConfigure(url); 181 } 182 else { 183 new ContextInitializer(loggerContext).configureByResource(url); 184 } 185 } 186 187 private void stopAndReset(LoggerContext loggerContext) { 188 loggerContext.stop(); 189 loggerContext.reset(); 190 if (isBridgeHandlerInstalled()) { 191 addLevelChangePropagator(loggerContext); 192 } 193 } 194 195 private boolean isBridgeHandlerInstalled() { 196 if (!isBridgeHandlerAvailable()) { 197 return false; 198 } 199 java.util.logging.Logger rootLogger = LogManager.getLogManager().getLogger(""); 200 Handler[] handlers = rootLogger.getHandlers(); 201 return handlers.length == 1 && SLF4JBridgeHandler.class.isInstance(handlers[0]); 202 } 203 204 private void addLevelChangePropagator(LoggerContext loggerContext) { 205 LevelChangePropagator levelChangePropagator = new LevelChangePropagator(); 206 levelChangePropagator.setResetJUL(true); 207 levelChangePropagator.setContext(loggerContext); 208 loggerContext.addListener(levelChangePropagator); 209 } 210 211 @Override 212 public void cleanUp() { 213 LoggerContext context = getLoggerContext(); 214 markAsUninitialized(context); 215 super.cleanUp(); 216 context.getStatusManager().clear(); 217 context.getTurboFilterList().remove(FILTER); 218 } 219 220 @Override 221 protected void reinitialize(LoggingInitializationContext initializationContext) { 222 getLoggerContext().reset(); 223 getLoggerContext().getStatusManager().clear(); 224 loadConfiguration(initializationContext, getSelfInitializationConfig(), null); 225 } 226 227 @Override 228 public List<LoggerConfiguration> getLoggerConfigurations() { 229 List<LoggerConfiguration> result = new ArrayList<>(); 230 for (ch.qos.logback.classic.Logger logger : getLoggerContext().getLoggerList()) { 231 result.add(getLoggerConfiguration(logger)); 232 } 233 result.sort(CONFIGURATION_COMPARATOR); 234 return result; 235 } 236 237 @Override 238 public LoggerConfiguration getLoggerConfiguration(String loggerName) { 239 return getLoggerConfiguration(getLogger(loggerName)); 240 } 241 242 private LoggerConfiguration getLoggerConfiguration( 243 ch.qos.logback.classic.Logger logger) { 244 if (logger == null) { 245 return null; 246 } 247 LogLevel level = LEVELS.convertNativeToSystem(logger.getLevel()); 248 LogLevel effectiveLevel = LEVELS 249 .convertNativeToSystem(logger.getEffectiveLevel()); 250 String name = logger.getName(); 251 if (!StringUtils.hasLength(name) || Logger.ROOT_LOGGER_NAME.equals(name)) { 252 name = ROOT_LOGGER_NAME; 253 } 254 return new LoggerConfiguration(name, level, effectiveLevel); 255 } 256 257 @Override 258 public Set<LogLevel> getSupportedLogLevels() { 259 return LEVELS.getSupported(); 260 } 261 262 @Override 263 public void setLogLevel(String loggerName, LogLevel level) { 264 ch.qos.logback.classic.Logger logger = getLogger(loggerName); 265 if (logger != null) { 266 logger.setLevel(LEVELS.convertSystemToNative(level)); 267 } 268 } 269 270 @Override 271 public Runnable getShutdownHandler() { 272 return new ShutdownHandler(); 273 } 274 275 private ch.qos.logback.classic.Logger getLogger(String name) { 276 LoggerContext factory = getLoggerContext(); 277 if (StringUtils.isEmpty(name) || ROOT_LOGGER_NAME.equals(name)) { 278 name = Logger.ROOT_LOGGER_NAME; 279 } 280 return factory.getLogger(name); 281 282 } 283 284 private LoggerContext getLoggerContext() { 285 ILoggerFactory factory = StaticLoggerBinder.getSingleton().getLoggerFactory(); 286 Assert.isInstanceOf(LoggerContext.class, factory, 287 String.format( 288 "LoggerFactory is not a Logback LoggerContext but Logback is on " 289 + "the classpath. Either remove Logback or the competing " 290 + "implementation (%s loaded from %s). If you are using " 291 + "WebLogic you will need to add 'org.slf4j' to " 292 + "prefer-application-packages in WEB-INF/weblogic.xml", 293 factory.getClass(), getLocation(factory))); 294 return (LoggerContext) factory; 295 } 296 297 private Object getLocation(ILoggerFactory factory) { 298 try { 299 ProtectionDomain protectionDomain = factory.getClass().getProtectionDomain(); 300 CodeSource codeSource = protectionDomain.getCodeSource(); 301 if (codeSource != null) { 302 return codeSource.getLocation(); 303 } 304 } 305 catch (SecurityException ex) { 306 // Unable to determine location 307 } 308 return "unknown location"; 309 } 310 311 private boolean isAlreadyInitialized(LoggerContext loggerContext) { 312 return loggerContext.getObject(LoggingSystem.class.getName()) != null; 313 } 314 315 private void markAsInitialized(LoggerContext loggerContext) { 316 loggerContext.putObject(LoggingSystem.class.getName(), new Object()); 317 } 318 319 private void markAsUninitialized(LoggerContext loggerContext) { 320 loggerContext.removeObject(LoggingSystem.class.getName()); 321 } 322 323 private final class ShutdownHandler implements Runnable { 324 325 @Override 326 public void run() { 327 getLoggerContext().stop(); 328 } 329 330 } 331 332}