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