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.log4j2; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.net.URL; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.List; 025import java.util.Set; 026 027import org.apache.logging.log4j.Level; 028import org.apache.logging.log4j.LogManager; 029import org.apache.logging.log4j.Marker; 030import org.apache.logging.log4j.core.Filter; 031import org.apache.logging.log4j.core.LogEvent; 032import org.apache.logging.log4j.core.Logger; 033import org.apache.logging.log4j.core.LoggerContext; 034import org.apache.logging.log4j.core.config.Configuration; 035import org.apache.logging.log4j.core.config.ConfigurationFactory; 036import org.apache.logging.log4j.core.config.ConfigurationSource; 037import org.apache.logging.log4j.core.config.LoggerConfig; 038import org.apache.logging.log4j.core.filter.AbstractFilter; 039import org.apache.logging.log4j.message.Message; 040 041import org.springframework.boot.logging.LogFile; 042import org.springframework.boot.logging.LogLevel; 043import org.springframework.boot.logging.LoggerConfiguration; 044import org.springframework.boot.logging.LoggingInitializationContext; 045import org.springframework.boot.logging.LoggingSystem; 046import org.springframework.boot.logging.Slf4JLoggingSystem; 047import org.springframework.util.Assert; 048import org.springframework.util.ClassUtils; 049import org.springframework.util.ResourceUtils; 050import org.springframework.util.StringUtils; 051 052/** 053 * {@link LoggingSystem} for <a href="http://logging.apache.org/log4j/2.x/">Log4j 2</a>. 054 * 055 * @author Daniel Fullarton 056 * @author Andy Wilkinson 057 * @author Alexander Heusingfeld 058 * @author Ben Hale 059 * @since 1.2.0 060 */ 061public class Log4J2LoggingSystem extends Slf4JLoggingSystem { 062 063 private static final String FILE_PROTOCOL = "file"; 064 065 private static final LogLevels<Level> LEVELS = new LogLevels<>(); 066 067 static { 068 LEVELS.map(LogLevel.TRACE, Level.TRACE); 069 LEVELS.map(LogLevel.DEBUG, Level.DEBUG); 070 LEVELS.map(LogLevel.INFO, Level.INFO); 071 LEVELS.map(LogLevel.WARN, Level.WARN); 072 LEVELS.map(LogLevel.ERROR, Level.ERROR); 073 LEVELS.map(LogLevel.FATAL, Level.FATAL); 074 LEVELS.map(LogLevel.OFF, Level.OFF); 075 } 076 077 private static final Filter FILTER = new AbstractFilter() { 078 079 @Override 080 public Result filter(LogEvent event) { 081 return Result.DENY; 082 } 083 084 @Override 085 public Result filter(Logger logger, Level level, Marker marker, Message msg, 086 Throwable t) { 087 return Result.DENY; 088 } 089 090 @Override 091 public Result filter(Logger logger, Level level, Marker marker, Object msg, 092 Throwable t) { 093 return Result.DENY; 094 } 095 096 @Override 097 public Result filter(Logger logger, Level level, Marker marker, String msg, 098 Object... params) { 099 return Result.DENY; 100 } 101 102 }; 103 104 public Log4J2LoggingSystem(ClassLoader classLoader) { 105 super(classLoader); 106 } 107 108 @Override 109 protected String[] getStandardConfigLocations() { 110 return getCurrentlySupportedConfigLocations(); 111 } 112 113 private String[] getCurrentlySupportedConfigLocations() { 114 List<String> supportedConfigLocations = new ArrayList<>(); 115 if (isClassAvailable("com.fasterxml.jackson.dataformat.yaml.YAMLParser")) { 116 Collections.addAll(supportedConfigLocations, "log4j2.yaml", "log4j2.yml"); 117 } 118 if (isClassAvailable("com.fasterxml.jackson.databind.ObjectMapper")) { 119 Collections.addAll(supportedConfigLocations, "log4j2.json", "log4j2.jsn"); 120 } 121 supportedConfigLocations.add("log4j2.xml"); 122 return StringUtils.toStringArray(supportedConfigLocations); 123 } 124 125 protected boolean isClassAvailable(String className) { 126 return ClassUtils.isPresent(className, getClassLoader()); 127 } 128 129 @Override 130 public void beforeInitialize() { 131 LoggerContext loggerContext = getLoggerContext(); 132 if (isAlreadyInitialized(loggerContext)) { 133 return; 134 } 135 super.beforeInitialize(); 136 loggerContext.getConfiguration().addFilter(FILTER); 137 } 138 139 @Override 140 public void initialize(LoggingInitializationContext initializationContext, 141 String configLocation, LogFile logFile) { 142 LoggerContext loggerContext = getLoggerContext(); 143 if (isAlreadyInitialized(loggerContext)) { 144 return; 145 } 146 loggerContext.getConfiguration().removeFilter(FILTER); 147 super.initialize(initializationContext, configLocation, logFile); 148 markAsInitialized(loggerContext); 149 } 150 151 @Override 152 protected void loadDefaults(LoggingInitializationContext initializationContext, 153 LogFile logFile) { 154 if (logFile != null) { 155 loadConfiguration(getPackagedConfigFile("log4j2-file.xml"), logFile); 156 } 157 else { 158 loadConfiguration(getPackagedConfigFile("log4j2.xml"), logFile); 159 } 160 } 161 162 @Override 163 protected void loadConfiguration(LoggingInitializationContext initializationContext, 164 String location, LogFile logFile) { 165 super.loadConfiguration(initializationContext, location, logFile); 166 loadConfiguration(location, logFile); 167 } 168 169 protected void loadConfiguration(String location, LogFile logFile) { 170 Assert.notNull(location, "Location must not be null"); 171 try { 172 LoggerContext ctx = getLoggerContext(); 173 URL url = ResourceUtils.getURL(location); 174 ConfigurationSource source = getConfigurationSource(url); 175 ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source)); 176 } 177 catch (Exception ex) { 178 throw new IllegalStateException( 179 "Could not initialize Log4J2 logging from " + location, ex); 180 } 181 } 182 183 private ConfigurationSource getConfigurationSource(URL url) throws IOException { 184 InputStream stream = url.openStream(); 185 if (FILE_PROTOCOL.equals(url.getProtocol())) { 186 return new ConfigurationSource(stream, ResourceUtils.getFile(url)); 187 } 188 return new ConfigurationSource(stream, url); 189 } 190 191 @Override 192 protected void reinitialize(LoggingInitializationContext initializationContext) { 193 getLoggerContext().reconfigure(); 194 } 195 196 @Override 197 public Set<LogLevel> getSupportedLogLevels() { 198 return LEVELS.getSupported(); 199 } 200 201 @Override 202 public void setLogLevel(String loggerName, LogLevel logLevel) { 203 Level level = LEVELS.convertSystemToNative(logLevel); 204 LoggerConfig loggerConfig = getLoggerConfig(loggerName); 205 if (loggerConfig == null) { 206 loggerConfig = new LoggerConfig(loggerName, level, true); 207 getLoggerContext().getConfiguration().addLogger(loggerName, loggerConfig); 208 } 209 else { 210 loggerConfig.setLevel(level); 211 } 212 getLoggerContext().updateLoggers(); 213 } 214 215 @Override 216 public List<LoggerConfiguration> getLoggerConfigurations() { 217 List<LoggerConfiguration> result = new ArrayList<>(); 218 Configuration configuration = getLoggerContext().getConfiguration(); 219 for (LoggerConfig loggerConfig : configuration.getLoggers().values()) { 220 result.add(convertLoggerConfiguration(loggerConfig)); 221 } 222 result.sort(CONFIGURATION_COMPARATOR); 223 return result; 224 } 225 226 @Override 227 public LoggerConfiguration getLoggerConfiguration(String loggerName) { 228 return convertLoggerConfiguration(getLoggerConfig(loggerName)); 229 } 230 231 private LoggerConfiguration convertLoggerConfiguration(LoggerConfig loggerConfig) { 232 if (loggerConfig == null) { 233 return null; 234 } 235 LogLevel level = LEVELS.convertNativeToSystem(loggerConfig.getLevel()); 236 String name = loggerConfig.getName(); 237 if (!StringUtils.hasLength(name) || LogManager.ROOT_LOGGER_NAME.equals(name)) { 238 name = ROOT_LOGGER_NAME; 239 } 240 return new LoggerConfiguration(name, level, level); 241 } 242 243 @Override 244 public Runnable getShutdownHandler() { 245 return new ShutdownHandler(); 246 } 247 248 @Override 249 public void cleanUp() { 250 super.cleanUp(); 251 LoggerContext loggerContext = getLoggerContext(); 252 markAsUninitialized(loggerContext); 253 loggerContext.getConfiguration().removeFilter(FILTER); 254 } 255 256 private LoggerConfig getLoggerConfig(String name) { 257 if (!StringUtils.hasLength(name) || ROOT_LOGGER_NAME.equals(name)) { 258 name = LogManager.ROOT_LOGGER_NAME; 259 } 260 return getLoggerContext().getConfiguration().getLoggers().get(name); 261 } 262 263 private LoggerContext getLoggerContext() { 264 return (LoggerContext) LogManager.getContext(false); 265 } 266 267 private boolean isAlreadyInitialized(LoggerContext loggerContext) { 268 return LoggingSystem.class.getName().equals(loggerContext.getExternalContext()); 269 } 270 271 private void markAsInitialized(LoggerContext loggerContext) { 272 loggerContext.setExternalContext(LoggingSystem.class.getName()); 273 } 274 275 private void markAsUninitialized(LoggerContext loggerContext) { 276 loggerContext.setExternalContext(null); 277 } 278 279 private final class ShutdownHandler implements Runnable { 280 281 @Override 282 public void run() { 283 getLoggerContext().stop(); 284 } 285 286 } 287 288}