001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.logging.log4j.core.config; 018 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Objects; 026import java.util.Set; 027import java.util.concurrent.CopyOnWriteArraySet; 028 029import org.apache.logging.log4j.Level; 030import org.apache.logging.log4j.LogManager; 031import org.apache.logging.log4j.Marker; 032import org.apache.logging.log4j.core.Appender; 033import org.apache.logging.log4j.core.Filter; 034import org.apache.logging.log4j.core.LogEvent; 035import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; 036import org.apache.logging.log4j.core.config.plugins.Plugin; 037import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 038import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 039import org.apache.logging.log4j.core.config.plugins.PluginElement; 040import org.apache.logging.log4j.core.config.plugins.PluginFactory; 041import org.apache.logging.log4j.core.filter.AbstractFilterable; 042import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; 043import org.apache.logging.log4j.core.impl.Log4jLogEvent; 044import org.apache.logging.log4j.core.impl.LogEventFactory; 045import org.apache.logging.log4j.core.lookup.StrSubstitutor; 046import org.apache.logging.log4j.core.util.Booleans; 047import org.apache.logging.log4j.core.util.Constants; 048import org.apache.logging.log4j.core.util.Loader; 049import org.apache.logging.log4j.message.Message; 050import org.apache.logging.log4j.util.PropertiesUtil; 051import org.apache.logging.log4j.util.Strings; 052 053/** 054 * Logger object that is created via configuration. 055 */ 056@Plugin(name = "logger", category = Node.CATEGORY, printObject = true) 057public class LoggerConfig extends AbstractFilterable { 058 059 public static final String ROOT = "root"; 060 private static final long serialVersionUID = 1L; 061 private static LogEventFactory LOG_EVENT_FACTORY = null; 062 063 private List<AppenderRef> appenderRefs = new ArrayList<>(); 064 private final Set<AppenderControl> appenders = new CopyOnWriteArraySet<>(); 065 private final String name; 066 private LogEventFactory logEventFactory; 067 private Level level; 068 private boolean additive = true; 069 private boolean includeLocation = true; 070 private LoggerConfig parent; 071 private final Map<Property, Boolean> properties; 072 private final Configuration config; 073 private final ReliabilityStrategy reliabilityStrategy; 074 075 static { 076 final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY); 077 if (factory != null) { 078 try { 079 final Class<?> clazz = Loader.loadClass(factory); 080 if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) { 081 LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance(); 082 } 083 } catch (final Exception ex) { 084 LOGGER.error("Unable to create LogEventFactory {}", factory, ex); 085 } 086 } 087 if (LOG_EVENT_FACTORY == null) { 088 LOG_EVENT_FACTORY = new DefaultLogEventFactory(); 089 } 090 } 091 092 /** 093 * Default constructor. 094 */ 095 public LoggerConfig() { 096 this.logEventFactory = LOG_EVENT_FACTORY; 097 this.level = Level.ERROR; 098 this.name = Strings.EMPTY; 099 this.properties = null; 100 this.config = null; 101 this.reliabilityStrategy = new DefaultReliabilityStrategy(this); 102 } 103 104 /** 105 * Constructor that sets the name, level and additive values. 106 * 107 * @param name The Logger name. 108 * @param level The Level. 109 * @param additive true if the Logger is additive, false otherwise. 110 */ 111 public LoggerConfig(final String name, final Level level, final boolean additive) { 112 this.logEventFactory = LOG_EVENT_FACTORY; 113 this.name = name; 114 this.level = level; 115 this.additive = additive; 116 this.properties = null; 117 this.config = null; 118 this.reliabilityStrategy = new DefaultReliabilityStrategy(this); 119 } 120 121 protected LoggerConfig(final String name, final List<AppenderRef> appenders, final Filter filter, 122 final Level level, final boolean additive, final Property[] properties, final Configuration config, 123 final boolean includeLocation) { 124 super(filter); 125 this.logEventFactory = LOG_EVENT_FACTORY; 126 this.name = name; 127 this.appenderRefs = appenders; 128 this.level = level; 129 this.additive = additive; 130 this.includeLocation = includeLocation; 131 this.config = config; 132 if (properties != null && properties.length > 0) { 133 this.properties = new HashMap<>(properties.length); 134 for (final Property prop : properties) { 135 final boolean interpolate = prop.getValue().contains("${"); 136 this.properties.put(prop, interpolate); 137 } 138 } else { 139 this.properties = null; 140 } 141 this.reliabilityStrategy = config.getReliabilityStrategy(this); 142 } 143 144 @Override 145 public Filter getFilter() { 146 return super.getFilter(); 147 } 148 149 /** 150 * Returns the name of the LoggerConfig. 151 * 152 * @return the name of the LoggerConfig. 153 */ 154 public String getName() { 155 return name; 156 } 157 158 /** 159 * Sets the parent of this LoggerConfig. 160 * 161 * @param parent the parent LoggerConfig. 162 */ 163 public void setParent(final LoggerConfig parent) { 164 this.parent = parent; 165 } 166 167 /** 168 * Returns the parent of this LoggerConfig. 169 * 170 * @return the LoggerConfig that is the parent of this one. 171 */ 172 public LoggerConfig getParent() { 173 return this.parent; 174 } 175 176 /** 177 * Adds an Appender to the LoggerConfig. 178 * 179 * @param appender The Appender to add. 180 * @param level The Level to use. 181 * @param filter A Filter for the Appender reference. 182 */ 183 public void addAppender(final Appender appender, final Level level, final Filter filter) { 184 appenders.add(new AppenderControl(appender, level, filter)); 185 } 186 187 /** 188 * Removes the Appender with the specific name. 189 * 190 * @param name The name of the Appender. 191 */ 192 public void removeAppender(final String name) { 193 for (final AppenderControl appenderControl : appenders) { 194 if (Objects.equals(name, appenderControl.getAppenderName())) { 195 if (appenders.remove(appenderControl)) { 196 cleanupFilter(appenderControl); 197 } 198 } 199 } 200 } 201 202 /** 203 * Returns all Appenders as a Map. 204 * 205 * @return a Map with the Appender name as the key and the Appender as the value. 206 */ 207 public Map<String, Appender> getAppenders() { 208 final Map<String, Appender> map = new HashMap<>(); 209 for (final AppenderControl appenderControl : appenders) { 210 map.put(appenderControl.getAppenderName(), appenderControl.getAppender()); 211 } 212 return map; 213 } 214 215 /** 216 * Removes all Appenders. 217 */ 218 protected void clearAppenders() { 219 List<AppenderControl> copy = new ArrayList<>(appenders); 220 while (!copy.isEmpty()) { 221 appenders.removeAll(copy); 222 for (final AppenderControl ctl : copy) { 223 cleanupFilter(ctl); 224 } 225 copy = new ArrayList<>(appenders); 226 } 227 } 228 229 private void cleanupFilter(final AppenderControl ctl) { 230 final Filter filter = ctl.getFilter(); 231 if (filter != null) { 232 ctl.removeFilter(filter); 233 filter.stop(); 234 } 235 } 236 237 /** 238 * Returns the Appender references. 239 * 240 * @return a List of all the Appender names attached to this LoggerConfig. 241 */ 242 public List<AppenderRef> getAppenderRefs() { 243 return appenderRefs; 244 } 245 246 /** 247 * Sets the logging Level. 248 * 249 * @param level The logging Level. 250 */ 251 public void setLevel(final Level level) { 252 this.level = level; 253 } 254 255 /** 256 * Returns the logging Level. 257 * 258 * @return the logging Level. 259 */ 260 public Level getLevel() { 261 return level == null ? parent.getLevel() : level; 262 } 263 264 /** 265 * Returns the LogEventFactory. 266 * 267 * @return the LogEventFactory. 268 */ 269 public LogEventFactory getLogEventFactory() { 270 return logEventFactory; 271 } 272 273 /** 274 * Sets the LogEventFactory. Usually the LogEventFactory will be this LoggerConfig. 275 * 276 * @param logEventFactory the LogEventFactory. 277 */ 278 public void setLogEventFactory(final LogEventFactory logEventFactory) { 279 this.logEventFactory = logEventFactory; 280 } 281 282 /** 283 * Returns the valid of the additive flag. 284 * 285 * @return true if the LoggerConfig is additive, false otherwise. 286 */ 287 public boolean isAdditive() { 288 return additive; 289 } 290 291 /** 292 * Sets the additive setting. 293 * 294 * @param additive true if the LoggerConfig should be additive, false otherwise. 295 */ 296 public void setAdditive(final boolean additive) { 297 this.additive = additive; 298 } 299 300 /** 301 * Returns the value of logger configuration attribute {@code includeLocation}, or, if no such attribute was 302 * configured, {@code true} if logging is synchronous or {@code false} if logging is asynchronous. 303 * 304 * @return whether location should be passed downstream 305 */ 306 public boolean isIncludeLocation() { 307 return includeLocation; 308 } 309 310 /** 311 * Returns an unmodifiable map with the configuration properties, or {@code null} if this {@code LoggerConfig} does 312 * not have any configuration properties. 313 * <p> 314 * For each {@code Property} key in the map, the value is {@code true} if the property value has a variable that 315 * needs to be substituted. 316 * 317 * @return an unmodifiable map with the configuration properties, or {@code null} 318 * @see Configuration#getStrSubstitutor() 319 * @see StrSubstitutor 320 */ 321 // LOG4J2-157 322 public Map<Property, Boolean> getProperties() { 323 return properties == null ? null : Collections.unmodifiableMap(properties); 324 } 325 326 /** 327 * Logs an event. 328 * 329 * @param loggerName The name of the Logger. 330 * @param fqcn The fully qualified class name of the caller. 331 * @param marker A Marker or null if none is present. 332 * @param level The event Level. 333 * @param data The Message. 334 * @param t A Throwable or null. 335 */ 336 public void log(final String loggerName, final String fqcn, final Marker marker, final Level level, 337 final Message data, final Throwable t) { 338 List<Property> props = null; 339 if (properties != null) { 340 props = new ArrayList<>(properties.size()); 341 Log4jLogEvent.Builder builder = new Log4jLogEvent.Builder(); 342 builder.setMessage(data).setMarker(marker).setLevel(level).setLoggerName(loggerName); 343 builder.setLoggerFqcn(fqcn).setThrown(t); 344 LogEvent event = builder.build(); 345 for (final Map.Entry<Property, Boolean> entry : properties.entrySet()) { 346 final Property prop = entry.getKey(); 347 final String value = entry.getValue() ? config.getStrSubstitutor().replace(event, prop.getValue()) 348 : prop.getValue(); 349 props.add(Property.createProperty(prop.getName(), value)); 350 } 351 } 352 log(logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t)); 353 } 354 355 /** 356 * Logs an event. 357 * 358 * @param event The log event. 359 */ 360 public void log(final LogEvent event) { 361 if (!isFiltered(event)) { 362 processLogEvent(event); 363 } 364 } 365 366 /** 367 * Returns the object responsible for ensuring log events are delivered to a working appender, even during or after 368 * a reconfiguration. 369 * 370 * @return the object responsible for delivery of log events to the appender 371 */ 372 public ReliabilityStrategy getReliabilityStrategy() { 373 return reliabilityStrategy; 374 } 375 376 private void processLogEvent(final LogEvent event) { 377 event.setIncludeLocation(isIncludeLocation()); 378 callAppenders(event); 379 logParent(event); 380 } 381 382 private void logParent(final LogEvent event) { 383 if (additive && parent != null) { 384 parent.log(event); 385 } 386 } 387 388 protected void callAppenders(final LogEvent event) { 389 for (final AppenderControl control : appenders) { 390 control.callAppender(event); 391 } 392 } 393 394 @Override 395 public String toString() { 396 return Strings.isEmpty(name) ? ROOT : name; 397 } 398 399 /** 400 * Factory method to create a LoggerConfig. 401 * 402 * @param additivity True if additive, false otherwise. 403 * @param level The Level to be associated with the Logger. 404 * @param loggerName The name of the Logger. 405 * @param includeLocation whether location should be passed downstream 406 * @param refs An array of Appender names. 407 * @param properties Properties to pass to the Logger. 408 * @param config The Configuration. 409 * @param filter A Filter. 410 * @return A new LoggerConfig. 411 */ 412 @PluginFactory 413 public static LoggerConfig createLogger(@PluginAttribute("additivity") final String additivity, 414 @PluginAttribute("level") final Level level, @PluginAttribute("name") final String loggerName, 415 @PluginAttribute("includeLocation") final String includeLocation, 416 @PluginElement("AppenderRef") final AppenderRef[] refs, 417 @PluginElement("Properties") final Property[] properties, @PluginConfiguration final Configuration config, 418 @PluginElement("Filter") final Filter filter) { 419 if (loggerName == null) { 420 LOGGER.error("Loggers cannot be configured without a name"); 421 return null; 422 } 423 424 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 425 final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; 426 final boolean additive = Booleans.parseBoolean(additivity, true); 427 428 return new LoggerConfig(name, appenderRefs, filter, level, additive, properties, config, 429 includeLocation(includeLocation)); 430 } 431 432 // Note: for asynchronous loggers, includeLocation default is FALSE, 433 // for synchronous loggers, includeLocation default is TRUE. 434 protected static boolean includeLocation(final String includeLocationConfigValue) { 435 if (includeLocationConfigValue == null) { 436 final boolean sync = !AsyncLoggerContextSelector.isSelected(); 437 return sync; 438 } 439 return Boolean.parseBoolean(includeLocationConfigValue); 440 } 441 442 /** 443 * The root Logger. 444 */ 445 @Plugin(name = ROOT, category = "Core", printObject = true) 446 public static class RootLogger extends LoggerConfig { 447 448 private static final long serialVersionUID = 1L; 449 450 @PluginFactory 451 public static LoggerConfig createLogger( 452 // @formatter:off 453 @PluginAttribute("additivity") final String additivity, 454 @PluginAttribute("level") final Level level, 455 @PluginAttribute("includeLocation") final String includeLocation, 456 @PluginElement("AppenderRef") final AppenderRef[] refs, 457 @PluginElement("Properties") final Property[] properties, 458 @PluginConfiguration final Configuration config, 459 @PluginElement("Filter") final Filter filter) { 460 // @formatter:on 461 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 462 final Level actualLevel = level == null ? Level.ERROR : level; 463 final boolean additive = Booleans.parseBoolean(additivity, true); 464 465 return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive, 466 properties, config, includeLocation(includeLocation)); 467 } 468 } 469 470}