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 List<Property> props = null; 385 if (!propertiesRequireLookup) { 386 props = properties; 387 } else { 388 if (properties != null) { 389 props = new ArrayList<>(properties.size()); 390 final LogEvent event = Log4jLogEvent.newBuilder() 391 .setMessage(data) 392 .setMarker(marker) 393 .setLevel(level) 394 .setLoggerName(loggerName) 395 .setLoggerFqcn(fqcn) 396 .setThrown(t) 397 .build(); 398 for (int i = 0; i < properties.size(); i++) { 399 final Property prop = properties.get(i); 400 final String value = prop.isValueNeedsLookup() // since LOG4J2-1575 401 ? config.getStrSubstitutor().replace(event, prop.getValue()) // 402 : prop.getValue(); 403 props.add(Property.createProperty(prop.getName(), value)); 404 } 405 } 406 } 407 final LogEvent logEvent = logEventFactory instanceof LocationAwareLogEventFactory ? 408 ((LocationAwareLogEventFactory) logEventFactory).createEvent(loggerName, marker, fqcn, requiresLocation() ? 409 StackLocatorUtil.calcLocation(fqcn) : null, level, data, props, t) : 410 logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t); 411 try { 412 log(logEvent, LoggerConfigPredicate.ALL); 413 } finally { 414 // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString()) 415 ReusableLogEventFactory.release(logEvent); 416 } 417 } 418 419 /** 420 * Logs an event. 421 * 422 * @param loggerName The name of the Logger. 423 * @param fqcn The fully qualified class name of the caller. 424 * @param location the location of the caller. 425 * @param marker A Marker or null if none is present. 426 * @param level The event Level. 427 * @param data The Message. 428 * @param t A Throwable or null. 429 */ 430 @PerformanceSensitive("allocation") 431 public void log(final String loggerName, final String fqcn, final StackTraceElement location, final Marker marker, 432 final Level level, final Message data, final Throwable t) { 433 List<Property> props = null; 434 if (!propertiesRequireLookup) { 435 props = properties; 436 } else { 437 if (properties != null) { 438 props = new ArrayList<>(properties.size()); 439 final LogEvent event = Log4jLogEvent.newBuilder() 440 .setMessage(data) 441 .setMarker(marker) 442 .setLevel(level) 443 .setLoggerName(loggerName) 444 .setLoggerFqcn(fqcn) 445 .setThrown(t) 446 .build(); 447 for (int i = 0; i < properties.size(); i++) { 448 final Property prop = properties.get(i); 449 final String value = prop.isValueNeedsLookup() // since LOG4J2-1575 450 ? config.getStrSubstitutor().replace(event, prop.getValue()) // 451 : prop.getValue(); 452 props.add(Property.createProperty(prop.getName(), value)); 453 } 454 } 455 } 456 final LogEvent logEvent = logEventFactory instanceof LocationAwareLogEventFactory ? 457 ((LocationAwareLogEventFactory) logEventFactory).createEvent(loggerName, marker, fqcn, location, level, 458 data, props, t) : logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t); 459 try { 460 log(logEvent, LoggerConfigPredicate.ALL); 461 } finally { 462 // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString()) 463 ReusableLogEventFactory.release(logEvent); 464 } 465 } 466 467 /** 468 * Logs an event. 469 * 470 * @param event The log event. 471 */ 472 public void log(final LogEvent event) { 473 log(event, LoggerConfigPredicate.ALL); 474 } 475 476 /** 477 * Logs an event. 478 * 479 * @param event The log event. 480 * @param predicate predicate for which LoggerConfig instances to append to. 481 * A null value is equivalent to a true predicate. 482 */ 483 protected void log(final LogEvent event, final LoggerConfigPredicate predicate) { 484 if (!isFiltered(event)) { 485 processLogEvent(event, predicate); 486 } 487 } 488 489 /** 490 * Returns the object responsible for ensuring log events are delivered to a working appender, even during or after 491 * a reconfiguration. 492 * 493 * @return the object responsible for delivery of log events to the appender 494 */ 495 public ReliabilityStrategy getReliabilityStrategy() { 496 return reliabilityStrategy; 497 } 498 499 private void processLogEvent(final LogEvent event, final LoggerConfigPredicate predicate) { 500 event.setIncludeLocation(isIncludeLocation()); 501 if (predicate.allow(this)) { 502 callAppenders(event); 503 } 504 logParent(event, predicate); 505 } 506 507 public boolean requiresLocation() { 508 if (!includeLocation) { 509 return false; 510 } 511 AppenderControl[] controls = appenders.get(); 512 LoggerConfig loggerConfig = this; 513 while (loggerConfig != null) { 514 for (AppenderControl control : controls) { 515 Appender appender = control.getAppender(); 516 if (appender instanceof LocationAware && ((LocationAware) appender).requiresLocation()) { 517 return true; 518 } 519 } 520 if (loggerConfig.additive) { 521 loggerConfig = loggerConfig.parent; 522 if (loggerConfig != null) { 523 controls = loggerConfig.appenders.get(); 524 } 525 } else { 526 break; 527 } 528 } 529 return false; 530 } 531 532 private void logParent(final LogEvent event, final LoggerConfigPredicate predicate) { 533 if (additive && parent != null) { 534 parent.log(event, predicate); 535 } 536 } 537 538 @PerformanceSensitive("allocation") 539 protected void callAppenders(final LogEvent event) { 540 final AppenderControl[] controls = appenders.get(); 541 //noinspection ForLoopReplaceableByForEach 542 for (int i = 0; i < controls.length; i++) { 543 controls[i].callAppender(event); 544 } 545 } 546 547 @Override 548 public String toString() { 549 return Strings.isEmpty(name) ? ROOT : name; 550 } 551 552 /** 553 * Factory method to create a LoggerConfig. 554 * 555 * @param additivity True if additive, false otherwise. 556 * @param level The Level to be associated with the Logger. 557 * @param loggerName The name of the Logger. 558 * @param includeLocation whether location should be passed downstream 559 * @param refs An array of Appender names. 560 * @param properties Properties to pass to the Logger. 561 * @param config The Configuration. 562 * @param filter A Filter. 563 * @return A new LoggerConfig. 564 * @deprecated Deprecated in 2.7; use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)} 565 */ 566 @Deprecated 567 public static LoggerConfig createLogger(final String additivity, 568 // @formatter:off 569 final Level level, 570 @PluginAttribute("name") final String loggerName, 571 final String includeLocation, 572 final AppenderRef[] refs, 573 final Property[] properties, 574 @PluginConfiguration final Configuration config, 575 final Filter filter) { 576 // @formatter:on 577 if (loggerName == null) { 578 LOGGER.error("Loggers cannot be configured without a name"); 579 return null; 580 } 581 582 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 583 final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; 584 final boolean additive = Booleans.parseBoolean(additivity, true); 585 586 return new LoggerConfig(name, appenderRefs, filter, level, additive, properties, config, 587 includeLocation(includeLocation, config)); 588 } 589 590 /** 591 * Factory method to create a LoggerConfig. 592 * 593 * @param additivity true if additive, false otherwise. 594 * @param level The Level to be associated with the Logger. 595 * @param loggerName The name of the Logger. 596 * @param includeLocation whether location should be passed downstream 597 * @param refs An array of Appender names. 598 * @param properties Properties to pass to the Logger. 599 * @param config The Configuration. 600 * @param filter A Filter. 601 * @return A new LoggerConfig. 602 * @since 2.6 603 */ 604 @PluginFactory 605 public static LoggerConfig createLogger( 606 // @formatter:off 607 @PluginAttribute(value = "additivity", defaultBoolean = true) final boolean additivity, 608 @PluginAttribute("level") final Level level, 609 @Required(message = "Loggers cannot be configured without a name") @PluginAttribute("name") final String loggerName, 610 @PluginAttribute("includeLocation") final String includeLocation, 611 @PluginElement("AppenderRef") final AppenderRef[] refs, 612 @PluginElement("Properties") final Property[] properties, 613 @PluginConfiguration final Configuration config, 614 @PluginElement("Filter") final Filter filter 615 // @formatter:on 616 ) { 617 final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName; 618 return new LoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config, 619 includeLocation(includeLocation, config)); 620 } 621 622 /** 623 * @deprecated Please use {@link #includeLocation(String, Configuration)} 624 */ 625 @Deprecated 626 protected static boolean includeLocation(final String includeLocationConfigValue) { 627 return includeLocation(includeLocationConfigValue, null); 628 } 629 630 // Note: for asynchronous loggers, includeLocation default is FALSE, 631 // for synchronous loggers, includeLocation default is TRUE. 632 protected static boolean includeLocation(final String includeLocationConfigValue, final Configuration configuration) { 633 if (includeLocationConfigValue == null) { 634 LoggerContext context = null; 635 if (configuration != null) { 636 context = configuration.getLoggerContext(); 637 } 638 if (context != null) { 639 return !(context instanceof AsyncLoggerContext); 640 } else { 641 return !AsyncLoggerContextSelector.isSelected(); 642 } 643 } 644 return Boolean.parseBoolean(includeLocationConfigValue); 645 } 646 647 protected final boolean hasAppenders() { 648 return !appenders.isEmpty(); 649 } 650 651 /** 652 * The root Logger. 653 */ 654 @Plugin(name = ROOT, category = Core.CATEGORY_NAME, printObject = true) 655 public static class RootLogger extends LoggerConfig { 656 657 @PluginFactory 658 public static LoggerConfig createLogger( 659 // @formatter:off 660 @PluginAttribute("additivity") final String additivity, 661 @PluginAttribute("level") final Level level, 662 @PluginAttribute("includeLocation") final String includeLocation, 663 @PluginElement("AppenderRef") final AppenderRef[] refs, 664 @PluginElement("Properties") final Property[] properties, 665 @PluginConfiguration final Configuration config, 666 @PluginElement("Filter") final Filter filter) { 667 // @formatter:on 668 final List<AppenderRef> appenderRefs = Arrays.asList(refs); 669 final Level actualLevel = level == null ? Level.ERROR : level; 670 final boolean additive = Booleans.parseBoolean(additivity, true); 671 672 return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive, 673 properties, config, includeLocation(includeLocation, config)); 674 } 675 } 676 677 protected enum LoggerConfigPredicate { 678 ALL() { 679 @Override 680 boolean allow(final LoggerConfig config) { 681 return true; 682 } 683 }, 684 ASYNCHRONOUS_ONLY() { 685 @Override 686 boolean allow(final LoggerConfig config) { 687 return config instanceof AsyncLoggerConfig; 688 } 689 }, 690 SYNCHRONOUS_ONLY() { 691 @Override 692 boolean allow(final LoggerConfig config) { 693 return !ASYNCHRONOUS_ONLY.allow(config); 694 } 695 }; 696 697 abstract boolean allow(LoggerConfig config); 698 } 699}