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