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