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; 025 026import org.apache.logging.log4j.Level; 027import org.apache.logging.log4j.LogManager; 028import org.apache.logging.log4j.Marker; 029import org.apache.logging.log4j.core.Appender; 030import org.apache.logging.log4j.core.Filter; 031import org.apache.logging.log4j.core.LogEvent; 032import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector; 033import org.apache.logging.log4j.core.config.plugins.Plugin; 034import org.apache.logging.log4j.core.config.plugins.PluginAttribute; 035import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; 036import org.apache.logging.log4j.core.config.plugins.PluginElement; 037import org.apache.logging.log4j.core.config.plugins.PluginFactory; 038import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; 039import org.apache.logging.log4j.core.filter.AbstractFilterable; 040import org.apache.logging.log4j.core.impl.DefaultLogEventFactory; 041import org.apache.logging.log4j.core.impl.Log4jLogEvent; 042import org.apache.logging.log4j.core.impl.LogEventFactory; 043import org.apache.logging.log4j.core.impl.ReusableLogEventFactory; 044import org.apache.logging.log4j.core.lookup.StrSubstitutor; 045import org.apache.logging.log4j.core.util.Booleans; 046import org.apache.logging.log4j.core.util.Constants; 047import org.apache.logging.log4j.core.util.Loader; 048import org.apache.logging.log4j.message.Message; 049import org.apache.logging.log4j.util.PerformanceSensitive; 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 LogEventFactory LOG_EVENT_FACTORY = null; 061 062 private List<AppenderRef> appenderRefs = new ArrayList<>(); 063 private final AppenderControlArraySet appenders = new AppenderControlArraySet(); 064 private final String name; 065 private LogEventFactory logEventFactory; 066 private Level level; 067 private boolean additive = true; 068 private boolean includeLocation = true; 069 private LoggerConfig parent; 070 private final Map<Property, Boolean> properties; 071 private final Configuration config; 072 private final ReliabilityStrategy reliabilityStrategy; 073 074 static { 075 final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY); 076 if (factory != null) { 077 try { 078 final Class<?> clazz = Loader.loadClass(factory); 079 if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) { 080 LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance(); 081 } 082 } catch (final Exception ex) { 083 LOGGER.error("Unable to create LogEventFactory {}", factory, ex); 084 } 085 } 086 if (LOG_EVENT_FACTORY == null) { 087 LOG_EVENT_FACTORY = Constants.ENABLE_THREADLOCALS 088 ? new ReusableLogEventFactory() 089 : new DefaultLogEventFactory(); 090 } 091 } 092 093 /** 094 * Default constructor. 095 */ 096 public LoggerConfig() { 097 this.logEventFactory = LOG_EVENT_FACTORY; 098 this.level = Level.ERROR; 099 this.name = Strings.EMPTY; 100 this.properties = null; 101 this.config = null; 102 this.reliabilityStrategy = new DefaultReliabilityStrategy(this); 103 } 104 105 /** 106 * Constructor that sets the name, level and additive values. 107 * 108 * @param name The Logger name. 109 * @param level The Level. 110 * @param additive true if the Logger is additive, false otherwise. 111 */ 112 public LoggerConfig(final String name, final Level level, final boolean additive) { 113 this.logEventFactory = LOG_EVENT_FACTORY; 114 this.name = name; 115 this.level = level; 116 this.additive = additive; 117 this.properties = null; 118 this.config = null; 119 this.reliabilityStrategy = new DefaultReliabilityStrategy(this); 120 } 121 122 protected LoggerConfig(final String name, final List<AppenderRef> appenders, final Filter filter, 123 final Level level, final boolean additive, final Property[] properties, final Configuration config, 124 final boolean includeLocation) { 125 super(filter); 126 this.logEventFactory = LOG_EVENT_FACTORY; 127 this.name = name; 128 this.appenderRefs = appenders; 129 this.level = level; 130 this.additive = additive; 131 this.includeLocation = includeLocation; 132 this.config = config; 133 if (properties != null && properties.length > 0) { 134 final Map<Property, Boolean> map = new HashMap<>(properties.length); 135 for (final Property prop : properties) { 136 final boolean interpolate = prop.getValue().contains("${"); 137 map.put(prop, interpolate); 138 } 139 this.properties = Collections.unmodifiableMap(map); 140 } else { 141 this.properties = null; 142 } 143 this.reliabilityStrategy = config.getReliabilityStrategy(this); 144 } 145 146 @Override 147 public Filter getFilter() { 148 return super.getFilter(); 149 } 150 151 /** 152 * Returns the name of the LoggerConfig. 153 * 154 * @return the name of the LoggerConfig. 155 */ 156 public String getName() { 157 return name; 158 } 159 160 /** 161 * Sets the parent of this LoggerConfig. 162 * 163 * @param parent the parent LoggerConfig. 164 */ 165 public void setParent(final LoggerConfig parent) { 166 this.parent = parent; 167 } 168 169 /** 170 * Returns the parent of this LoggerConfig. 171 * 172 * @return the LoggerConfig that is the parent of this one. 173 */ 174 public LoggerConfig getParent() { 175 return this.parent; 176 } 177 178 /** 179 * Adds an Appender to the LoggerConfig. 180 * 181 * @param appender The Appender to add. 182 * @param level The Level to use. 183 * @param filter A Filter for the Appender reference. 184 */ 185 public void addAppender(final Appender appender, final Level level, final Filter filter) { 186 appenders.add(new AppenderControl(appender, level, filter)); 187 } 188 189 /** 190 * Removes the Appender with the specific name. 191 * 192 * @param name The name of the Appender. 193 */ 194 public void removeAppender(final String name) { 195 AppenderControl removed = null; 196 while ((removed = appenders.remove(name)) != null) { 197 cleanupFilter(removed); 198 } 199 } 200 201 /** 202 * Returns all Appenders as a Map. 203 * 204 * @return a Map with the Appender name as the key and the Appender as the value. 205 */ 206 public Map<String, Appender> getAppenders() { 207 return appenders.asMap(); 208 } 209 210 /** 211 * Removes all Appenders. 212 */ 213 protected void clearAppenders() { 214 do { 215 final AppenderControl[] original = appenders.clear(); 216 for (final AppenderControl ctl : original) { 217 cleanupFilter(ctl); 218 } 219 } while (!appenders.isEmpty()); 220 } 221 222 private void cleanupFilter(final AppenderControl ctl) { 223 final Filter filter = ctl.getFilter(); 224 if (filter != null) { 225 ctl.removeFilter(filter); 226 filter.stop(); 227 } 228 } 229 230 /** 231 * Returns the Appender references. 232 * 233 * @return a List of all the Appender names attached to this LoggerConfig. 234 */ 235 public List<AppenderRef> getAppenderRefs() { 236 return appenderRefs; 237 } 238 239 /** 240 * Sets the logging Level. 241 * 242 * @param level The logging Level. 243 */ 244 public void setLevel(final Level level) { 245 this.level = level; 246 } 247 248 /** 249 * Returns the logging Level. 250 * 251 * @return the logging Level. 252 */ 253 public Level getLevel() { 254 return level == null ? parent.getLevel() : level; 255 } 256 257 /** 258 * Returns the LogEventFactory. 259 * 260 * @return the LogEventFactory. 261 */ 262 public LogEventFactory getLogEventFactory() { 263 return logEventFactory; 264 } 265 266 /** 267 * Sets the LogEventFactory. Usually the LogEventFactory will be this LoggerConfig. 268 * 269 * @param logEventFactory the LogEventFactory. 270 */ 271 public void setLogEventFactory(final LogEventFactory logEventFactory) { 272 this.logEventFactory = logEventFactory; 273 } 274 275 /** 276 * Returns the valid of the additive flag. 277 * 278 * @return true if the LoggerConfig is additive, false otherwise. 279 */ 280 public boolean isAdditive() { 281 return additive; 282 } 283 284 /** 285 * Sets the additive setting. 286 * 287 * @param additive true if the LoggerConfig should be additive, false otherwise. 288 */ 289 public void setAdditive(final boolean additive) { 290 this.additive = additive; 291 } 292 293 /** 294 * Returns the value of logger configuration attribute {@code includeLocation}, or, if no such attribute was 295 * configured, {@code true} if logging is synchronous or {@code false} if logging is asynchronous. 296 * 297 * @return whether location should be passed downstream 298 */ 299 public boolean isIncludeLocation() { 300 return includeLocation; 301 } 302 303 /** 304 * Returns an unmodifiable map with the configuration properties, or {@code null} if this {@code LoggerConfig} does 305 * not have any configuration properties. 306 * <p> 307 * For each {@code Property} key in the map, the value is {@code true} if the property value has a variable that 308 * needs to be substituted. 309 * 310 * @return an unmodifiable map with the configuration properties, or {@code null} 311 * @see Configuration#getStrSubstitutor() 312 * @see StrSubstitutor 313 */ 314 // LOG4J2-157 315 public Map<Property, Boolean> getProperties() { 316 return properties; 317 } 318 319 /** 320 * Logs an event. 321 * 322 * @param loggerName The name of the Logger. 323 * @param fqcn The fully qualified class name of the caller. 324 * @param marker A Marker or null if none is present. 325 * @param level The event Level. 326 * @param data The Message. 327 * @param t A Throwable or null. 328 */ 329 public void log(final String loggerName, final String fqcn, final Marker marker, final Level level, 330 final Message data, final Throwable t) { 331 List<Property> props = null; 332 if (properties != null) { 333 props = new ArrayList<>(properties.size()); 334 final LogEvent event = Log4jLogEvent.newBuilder() 335 .setMessage(data) 336 .setMarker(marker) 337 .setLevel(level) 338 .setLoggerName(loggerName) 339 .setLoggerFqcn(fqcn) 340 .setThrown(t) 341 .build(); 342 for (final Map.Entry<Property, Boolean> entry : properties.entrySet()) { 343 final Property prop = entry.getKey(); 344 final String value = entry.getValue() ? config.getStrSubstitutor().replace(event, prop.getValue()) 345 : prop.getValue(); 346 props.add(Property.createProperty(prop.getName(), value)); 347 } 348 } 349 log(logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t)); 350 } 351 352 /** 353 * Logs an event. 354 * 355 * @param event The log event. 356 */ 357 public void log(final LogEvent event) { 358 if (!isFiltered(event)) { 359 processLogEvent(event); 360 } 361 } 362 363 /** 364 * Returns the object responsible for ensuring log events are delivered to a working appender, even during or after 365 * a reconfiguration. 366 * 367 * @return the object responsible for delivery of log events to the appender 368 */ 369 public ReliabilityStrategy getReliabilityStrategy() { 370 return reliabilityStrategy; 371 } 372 373 private void processLogEvent(final LogEvent event) { 374 event.setIncludeLocation(isIncludeLocation()); 375 callAppenders(event); 376 logParent(event); 377 } 378 379 private void logParent(final LogEvent event) { 380 if (additive && parent != null) { 381 parent.log(event); 382 } 383 } 384 385 @PerformanceSensitive("allocation") 386 protected void callAppenders(final LogEvent event) { 387 final AppenderControl[] controls = appenders.get(); 388 //noinspection ForLoopReplaceableByForEach 389 for (int i = 0; i < controls.length; i++) { 390 controls[i].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 * @deprecated Use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)} 412 */ 413 @Deprecated 414 public static LoggerConfig createLogger(@PluginAttribute("additivity") final String additivity, 415 @PluginAttribute("level") final Level level, @PluginAttribute("name") final String loggerName, 416 @PluginAttribute("includeLocation") final String includeLocation, 417 @PluginElement("AppenderRef") final AppenderRef[] refs, 418 @PluginElement("Properties") final Property[] properties, @PluginConfiguration final Configuration config, 419 @PluginElement("Filter") final Filter filter) { 420 if (loggerName == null) { 421 LOGGER.error("Loggers cannot be configured without a name"); 422 return null; 423 } 424 425 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 426 final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; 427 final boolean additive = Booleans.parseBoolean(additivity, true); 428 429 return new LoggerConfig(name, appenderRefs, filter, level, additive, properties, config, 430 includeLocation(includeLocation)); 431 } 432 433 /** 434 * Factory method to create a LoggerConfig. 435 * 436 * @param additivity True if additive, false otherwise. 437 * @param level The Level to be associated with the Logger. 438 * @param loggerName The name of the Logger. 439 * @param includeLocation whether location should be passed downstream 440 * @param refs An array of Appender names. 441 * @param properties Properties to pass to the Logger. 442 * @param config The Configuration. 443 * @param filter A Filter. 444 * @return A new LoggerConfig. 445 * @since 2.6 446 */ 447 @PluginFactory 448 public static LoggerConfig createLogger( 449 @PluginAttribute(value = "additivity", defaultBoolean = true) final boolean additivity, 450 @PluginAttribute("level") final Level level, 451 @Required(message = "Loggers cannot be configured without a name") @PluginAttribute("name") final String loggerName, 452 @PluginAttribute("includeLocation") final String includeLocation, 453 @PluginElement("AppenderRef") final AppenderRef[] refs, 454 @PluginElement("Properties") final Property[] properties, 455 @PluginConfiguration final Configuration config, 456 @PluginElement("Filter") final Filter filter 457 ) { 458 final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; 459 return new LoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config, 460 includeLocation(includeLocation)); 461 } 462 463 // Note: for asynchronous loggers, includeLocation default is FALSE, 464 // for synchronous loggers, includeLocation default is TRUE. 465 protected static boolean includeLocation(final String includeLocationConfigValue) { 466 if (includeLocationConfigValue == null) { 467 final boolean sync = !AsyncLoggerContextSelector.isSelected(); 468 return sync; 469 } 470 return Boolean.parseBoolean(includeLocationConfigValue); 471 } 472 473 /** 474 * The root Logger. 475 */ 476 @Plugin(name = ROOT, category = "Core", printObject = true) 477 public static class RootLogger extends LoggerConfig { 478 479 @PluginFactory 480 public static LoggerConfig createLogger( 481 // @formatter:off 482 @PluginAttribute("additivity") final String additivity, 483 @PluginAttribute("level") final Level level, 484 @PluginAttribute("includeLocation") final String includeLocation, 485 @PluginElement("AppenderRef") final AppenderRef[] refs, 486 @PluginElement("Properties") final Property[] properties, 487 @PluginConfiguration final Configuration config, 488 @PluginElement("Filter") final Filter filter) { 489 // @formatter:on 490 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 491 final Level actualLevel = level == null ? Level.ERROR : level; 492 final boolean additive = Booleans.parseBoolean(additivity, true); 493 494 return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive, 495 properties, config, includeLocation(includeLocation)); 496 } 497 } 498 499}