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