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.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.Serializable; 023import java.lang.ref.WeakReference; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.HashSet; 029import java.util.LinkedHashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.Objects; 033import java.util.Set; 034import java.util.concurrent.ConcurrentHashMap; 035import java.util.concurrent.ConcurrentMap; 036import java.util.concurrent.CopyOnWriteArrayList; 037import java.util.concurrent.TimeUnit; 038 039import org.apache.logging.log4j.Level; 040import org.apache.logging.log4j.core.Appender; 041import org.apache.logging.log4j.core.Filter; 042import org.apache.logging.log4j.core.Layout; 043import org.apache.logging.log4j.core.LifeCycle2; 044import org.apache.logging.log4j.core.LogEvent; 045import org.apache.logging.log4j.core.LoggerContext; 046import org.apache.logging.log4j.core.Version; 047import org.apache.logging.log4j.core.appender.AsyncAppender; 048import org.apache.logging.log4j.core.appender.ConsoleAppender; 049import org.apache.logging.log4j.core.async.AsyncLoggerConfig; 050import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate; 051import org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor; 052import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder; 053import org.apache.logging.log4j.core.config.plugins.util.PluginManager; 054import org.apache.logging.log4j.core.config.plugins.util.PluginType; 055import org.apache.logging.log4j.core.filter.AbstractFilterable; 056import org.apache.logging.log4j.core.layout.PatternLayout; 057import org.apache.logging.log4j.core.lookup.Interpolator; 058import org.apache.logging.log4j.core.lookup.MapLookup; 059import org.apache.logging.log4j.core.lookup.StrLookup; 060import org.apache.logging.log4j.core.lookup.StrSubstitutor; 061import org.apache.logging.log4j.core.net.Advertiser; 062import org.apache.logging.log4j.core.script.AbstractScript; 063import org.apache.logging.log4j.core.script.ScriptManager; 064import org.apache.logging.log4j.core.script.ScriptRef; 065import org.apache.logging.log4j.core.util.Constants; 066import org.apache.logging.log4j.core.util.DummyNanoClock; 067import org.apache.logging.log4j.core.util.Loader; 068import org.apache.logging.log4j.core.util.NameUtil; 069import org.apache.logging.log4j.core.util.NanoClock; 070import org.apache.logging.log4j.core.util.WatchManager; 071import org.apache.logging.log4j.util.PropertiesUtil; 072 073/** 074 * The base Configuration. Many configuration implementations will extend this class. 075 */ 076public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration { 077 078 private static final int BUF_SIZE = 16384; 079 080 /** 081 * The root node of the configuration. 082 */ 083 protected Node rootNode; 084 085 /** 086 * Listeners for configuration changes. 087 */ 088 protected final List<ConfigurationListener> listeners = new CopyOnWriteArrayList<>(); 089 090 /** 091 * Packages found in configuration "packages" attribute. 092 */ 093 protected final List<String> pluginPackages = new ArrayList<>(); 094 095 /** 096 * The plugin manager. 097 */ 098 protected PluginManager pluginManager; 099 100 /** 101 * Shutdown hook is enabled by default. 102 */ 103 protected boolean isShutdownHookEnabled = true; 104 105 /** 106 * Shutdown timeout in milliseconds. 107 */ 108 protected long shutdownTimeoutMillis = 0; 109 110 /** 111 * The Script manager. 112 */ 113 protected ScriptManager scriptManager; 114 115 /** 116 * The Advertiser which exposes appender configurations to external systems. 117 */ 118 private Advertiser advertiser = new DefaultAdvertiser(); 119 private Node advertiserNode = null; 120 private Object advertisement; 121 private String name; 122 private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<>(); 123 private ConcurrentMap<String, LoggerConfig> loggerConfigs = new ConcurrentHashMap<>(); 124 private List<CustomLevelConfig> customLevels = Collections.emptyList(); 125 private final ConcurrentMap<String, String> propertyMap = new ConcurrentHashMap<>(); 126 private final StrLookup tempLookup = new Interpolator(propertyMap); 127 private final StrSubstitutor subst = new StrSubstitutor(tempLookup); 128 private LoggerConfig root = new LoggerConfig(); 129 private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<>(); 130 private final ConfigurationSource configurationSource; 131 private final ConfigurationScheduler configurationScheduler = new ConfigurationScheduler(); 132 private final WatchManager watchManager = new WatchManager(configurationScheduler); 133 private AsyncLoggerConfigDisruptor asyncLoggerConfigDisruptor; 134 private NanoClock nanoClock = new DummyNanoClock(); 135 private final WeakReference<LoggerContext> loggerContext; 136 137 /** 138 * Constructor. 139 */ 140 protected AbstractConfiguration(final LoggerContext loggerContext, final ConfigurationSource configurationSource) { 141 this.loggerContext = new WeakReference<>(loggerContext); 142 // The loggerContext is null for the NullConfiguration class. 143 // this.loggerContext = new WeakReference(Objects.requireNonNull(loggerContext, "loggerContext is null")); 144 this.configurationSource = Objects.requireNonNull(configurationSource, "configurationSource is null"); 145 componentMap.put(Configuration.CONTEXT_PROPERTIES, propertyMap); 146 pluginManager = new PluginManager(Node.CATEGORY); 147 rootNode = new Node(); 148 setState(State.INITIALIZING); 149 150 } 151 152 @Override 153 public ConfigurationSource getConfigurationSource() { 154 return configurationSource; 155 } 156 157 @Override 158 public List<String> getPluginPackages() { 159 return pluginPackages; 160 } 161 162 @Override 163 public Map<String, String> getProperties() { 164 return propertyMap; 165 } 166 167 @Override 168 public ScriptManager getScriptManager() { 169 return scriptManager; 170 } 171 172 public void setScriptManager(final ScriptManager scriptManager) { 173 this.scriptManager = scriptManager; 174 } 175 176 public PluginManager getPluginManager() { 177 return pluginManager; 178 } 179 180 public void setPluginManager(final PluginManager pluginManager) { 181 this.pluginManager = pluginManager; 182 } 183 184 @Override 185 public WatchManager getWatchManager() { 186 return watchManager; 187 } 188 189 @Override 190 public ConfigurationScheduler getScheduler() { 191 return configurationScheduler; 192 } 193 194 public Node getRootNode() { 195 return rootNode; 196 } 197 198 @Override 199 public AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() { 200 // lazily instantiate only when requested by AsyncLoggers: 201 // loading AsyncLoggerConfigDisruptor requires LMAX Disruptor jar on classpath 202 if (asyncLoggerConfigDisruptor == null) { 203 asyncLoggerConfigDisruptor = new AsyncLoggerConfigDisruptor(); 204 } 205 return asyncLoggerConfigDisruptor; 206 } 207 208 /** 209 * Initialize the configuration. 210 */ 211 @Override 212 public void initialize() { 213 LOGGER.debug(Version.getProductString() + " initializing configuration {}", this); 214 subst.setConfiguration(this); 215 try { 216 scriptManager = new ScriptManager(this, watchManager); 217 } catch (final LinkageError | Exception e) { 218 // LOG4J2-1920 ScriptEngineManager is not available in Android 219 LOGGER.info("Cannot initialize scripting support because this JRE does not support it.", e); 220 } 221 pluginManager.collectPlugins(pluginPackages); 222 final PluginManager levelPlugins = new PluginManager(Level.CATEGORY); 223 levelPlugins.collectPlugins(pluginPackages); 224 final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins(); 225 if (plugins != null) { 226 for (final PluginType<?> type : plugins.values()) { 227 try { 228 // Cause the class to be initialized if it isn't already. 229 Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader()); 230 } catch (final Exception e) { 231 LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass() 232 .getSimpleName(), e); 233 } 234 } 235 } 236 setup(); 237 setupAdvertisement(); 238 doConfigure(); 239 setState(State.INITIALIZED); 240 LOGGER.debug("Configuration {} initialized", this); 241 } 242 243 /** 244 * Start the configuration. 245 */ 246 @Override 247 public void start() { 248 // Preserve the prior behavior of initializing during start if not initialized. 249 if (getState().equals(State.INITIALIZING)) { 250 initialize(); 251 } 252 LOGGER.debug("Starting configuration {}", this); 253 this.setStarting(); 254 if (watchManager.getIntervalSeconds() > 0) { 255 watchManager.start(); 256 } 257 if (hasAsyncLoggers()) { 258 asyncLoggerConfigDisruptor.start(); 259 } 260 final Set<LoggerConfig> alreadyStarted = new HashSet<>(); 261 for (final LoggerConfig logger : loggerConfigs.values()) { 262 logger.start(); 263 alreadyStarted.add(logger); 264 } 265 for (final Appender appender : appenders.values()) { 266 appender.start(); 267 } 268 if (!alreadyStarted.contains(root)) { // LOG4J2-392 269 root.start(); // LOG4J2-336 270 } 271 super.start(); 272 LOGGER.debug("Started configuration {} OK.", this); 273 } 274 275 private boolean hasAsyncLoggers() { 276 if (root instanceof AsyncLoggerConfig) { 277 return true; 278 } 279 for (final LoggerConfig logger : loggerConfigs.values()) { 280 if (logger instanceof AsyncLoggerConfig) { 281 return true; 282 } 283 } 284 return false; 285 } 286 287 /** 288 * Tear down the configuration. 289 */ 290 @Override 291 public boolean stop(final long timeout, final TimeUnit timeUnit) { 292 this.setStopping(); 293 super.stop(timeout, timeUnit, false); 294 LOGGER.trace("Stopping {}...", this); 295 296 // Stop the components that are closest to the application first: 297 // 1. Notify all LoggerConfigs' ReliabilityStrategy that the configuration will be stopped. 298 // 2. Stop the LoggerConfig objects (this may stop nested Filters) 299 // 3. Stop the AsyncLoggerConfigDelegate. This shuts down the AsyncLoggerConfig Disruptor 300 // and waits until all events in the RingBuffer have been processed. 301 // 4. Stop all AsyncAppenders. This shuts down the associated thread and 302 // waits until all events in the queue have been processed. (With optional timeout.) 303 // 5. Notify all LoggerConfigs' ReliabilityStrategy that appenders will be stopped. 304 // This guarantees that any event received by a LoggerConfig before reconfiguration 305 // are passed on to the Appenders before the Appenders are stopped. 306 // 6. Stop the remaining running Appenders. (It should now be safe to do so.) 307 // 7. Notify all LoggerConfigs that their Appenders can be cleaned up. 308 309 for (final LoggerConfig loggerConfig : loggerConfigs.values()) { 310 loggerConfig.getReliabilityStrategy().beforeStopConfiguration(this); 311 } 312 root.getReliabilityStrategy().beforeStopConfiguration(this); 313 314 final String cls = getClass().getSimpleName(); 315 LOGGER.trace("{} notified {} ReliabilityStrategies that config will be stopped.", cls, loggerConfigs.size() 316 + 1); 317 318 if (!loggerConfigs.isEmpty()) { 319 LOGGER.trace("{} stopping {} LoggerConfigs.", cls, loggerConfigs.size()); 320 for (final LoggerConfig logger : loggerConfigs.values()) { 321 logger.stop(timeout, timeUnit); 322 } 323 } 324 LOGGER.trace("{} stopping root LoggerConfig.", cls); 325 if (!root.isStopped()) { 326 root.stop(timeout, timeUnit); 327 } 328 329 if (hasAsyncLoggers()) { 330 LOGGER.trace("{} stopping AsyncLoggerConfigDisruptor.", cls); 331 asyncLoggerConfigDisruptor.stop(timeout, timeUnit); 332 } 333 334 // Stop the appenders in reverse order in case they still have activity. 335 final Appender[] array = appenders.values().toArray(new Appender[appenders.size()]); 336 final List<Appender> async = getAsyncAppenders(array); 337 if (!async.isEmpty()) { 338 // LOG4J2-511, LOG4J2-392 stop AsyncAppenders first 339 LOGGER.trace("{} stopping {} AsyncAppenders.", cls, async.size()); 340 for (final Appender appender : async) { 341 if (appender instanceof LifeCycle2) { 342 ((LifeCycle2) appender).stop(timeout, timeUnit); 343 } else { 344 appender.stop(); 345 } 346 } 347 } 348 349 LOGGER.trace("{} notifying ReliabilityStrategies that appenders will be stopped.", cls); 350 for (final LoggerConfig loggerConfig : loggerConfigs.values()) { 351 loggerConfig.getReliabilityStrategy().beforeStopAppenders(); 352 } 353 root.getReliabilityStrategy().beforeStopAppenders(); 354 355 LOGGER.trace("{} stopping remaining Appenders.", cls); 356 int appenderCount = 0; 357 for (int i = array.length - 1; i >= 0; --i) { 358 if (array[i].isStarted()) { // then stop remaining Appenders 359 if (array[i] instanceof LifeCycle2) { 360 ((LifeCycle2) array[i]).stop(timeout, timeUnit); 361 } else { 362 array[i].stop(); 363 } 364 appenderCount++; 365 } 366 } 367 LOGGER.trace("{} stopped {} remaining Appenders.", cls, appenderCount); 368 369 LOGGER.trace("{} cleaning Appenders from {} LoggerConfigs.", cls, loggerConfigs.size() + 1); 370 for (final LoggerConfig loggerConfig : loggerConfigs.values()) { 371 372 // LOG4J2-520, LOG4J2-392: 373 // Important: do not clear appenders until after all AsyncLoggerConfigs 374 // have been stopped! Stopping the last AsyncLoggerConfig will 375 // shut down the disruptor and wait for all enqueued events to be processed. 376 // Only *after this* the appenders can be cleared or events will be lost. 377 loggerConfig.clearAppenders(); 378 } 379 root.clearAppenders(); 380 381 if (watchManager.isStarted()) { 382 watchManager.stop(timeout, timeUnit); 383 } 384 configurationScheduler.stop(timeout, timeUnit); 385 386 if (advertiser != null && advertisement != null) { 387 advertiser.unadvertise(advertisement); 388 } 389 setStopped(); 390 LOGGER.debug("Stopped {} OK", this); 391 return true; 392 } 393 394 private List<Appender> getAsyncAppenders(final Appender[] all) { 395 final List<Appender> result = new ArrayList<>(); 396 for (int i = all.length - 1; i >= 0; --i) { 397 if (all[i] instanceof AsyncAppender) { 398 result.add(all[i]); 399 } 400 } 401 return result; 402 } 403 404 @Override 405 public boolean isShutdownHookEnabled() { 406 return isShutdownHookEnabled; 407 } 408 409 @Override 410 public long getShutdownTimeoutMillis() { 411 return shutdownTimeoutMillis; 412 } 413 414 public void setup() { 415 // default does nothing, subclasses do work. 416 } 417 418 protected Level getDefaultStatus() { 419 final String statusLevel = PropertiesUtil.getProperties().getStringProperty( 420 Constants.LOG4J_DEFAULT_STATUS_LEVEL, Level.ERROR.name()); 421 try { 422 return Level.toLevel(statusLevel); 423 } catch (final Exception ex) { 424 return Level.ERROR; 425 } 426 } 427 428 protected void createAdvertiser(final String advertiserString, final ConfigurationSource configSource, 429 final byte[] buffer, final String contentType) { 430 if (advertiserString != null) { 431 final Node node = new Node(null, advertiserString, null); 432 final Map<String, String> attributes = node.getAttributes(); 433 attributes.put("content", new String(buffer)); 434 attributes.put("contentType", contentType); 435 attributes.put("name", "configuration"); 436 if (configSource.getLocation() != null) { 437 attributes.put("location", configSource.getLocation()); 438 } 439 advertiserNode = node; 440 } 441 } 442 443 private void setupAdvertisement() { 444 if (advertiserNode != null) { 445 final String nodeName = advertiserNode.getName(); 446 final PluginType<?> type = pluginManager.getPluginType(nodeName); 447 if (type != null) { 448 final Class<? extends Advertiser> clazz = type.getPluginClass().asSubclass(Advertiser.class); 449 try { 450 advertiser = clazz.newInstance(); 451 advertisement = advertiser.advertise(advertiserNode.getAttributes()); 452 } catch (final InstantiationException e) { 453 LOGGER.error("InstantiationException attempting to instantiate advertiser: {}", nodeName, e); 454 } catch (final IllegalAccessException e) { 455 LOGGER.error("IllegalAccessException attempting to instantiate advertiser: {}", nodeName, e); 456 } 457 } 458 } 459 } 460 461 @SuppressWarnings("unchecked") 462 @Override 463 public <T> T getComponent(final String componentName) { 464 return (T) componentMap.get(componentName); 465 } 466 467 @Override 468 public void addComponent(final String componentName, final Object obj) { 469 componentMap.putIfAbsent(componentName, obj); 470 } 471 472 protected void preConfigure(final Node node) { 473 try { 474 for (final Node child : node.getChildren()) { 475 if (child.getType() == null) { 476 LOGGER.error("Unable to locate plugin type for " + child.getName()); 477 continue; 478 } 479 final Class<?> clazz = child.getType().getPluginClass(); 480 if (clazz.isAnnotationPresent(Scheduled.class)) { 481 configurationScheduler.incrementScheduledItems(); 482 } 483 preConfigure(child); 484 } 485 } catch (final Exception ex) { 486 LOGGER.error("Error capturing node data for node " + node.getName(), ex); 487 } 488 } 489 490 protected void doConfigure() { 491 preConfigure(rootNode); 492 configurationScheduler.start(); 493 if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) { 494 final Node first = rootNode.getChildren().get(0); 495 createConfiguration(first, null); 496 if (first.getObject() != null) { 497 subst.setVariableResolver((StrLookup) first.getObject()); 498 } 499 } else { 500 final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES); 501 final StrLookup lookup = map == null ? null : new MapLookup(map); 502 subst.setVariableResolver(new Interpolator(lookup, pluginPackages)); 503 } 504 505 boolean setLoggers = false; 506 boolean setRoot = false; 507 for (final Node child : rootNode.getChildren()) { 508 if (child.getName().equalsIgnoreCase("Properties")) { 509 if (tempLookup == subst.getVariableResolver()) { 510 LOGGER.error("Properties declaration must be the first element in the configuration"); 511 } 512 continue; 513 } 514 createConfiguration(child, null); 515 if (child.getObject() == null) { 516 continue; 517 } 518 if (child.getName().equalsIgnoreCase("Scripts")) { 519 for (final AbstractScript script : child.getObject(AbstractScript[].class)) { 520 if (script instanceof ScriptRef) { 521 LOGGER.error("Script reference to {} not added. Scripts definition cannot contain script references", 522 script.getName()); 523 } else { 524 if (scriptManager != null) { 525 scriptManager.addScript(script); 526 }} 527 } 528 } else if (child.getName().equalsIgnoreCase("Appenders")) { 529 appenders = child.getObject(); 530 } else if (child.isInstanceOf(Filter.class)) { 531 addFilter(child.getObject(Filter.class)); 532 } else if (child.getName().equalsIgnoreCase("Loggers")) { 533 final Loggers l = child.getObject(); 534 loggerConfigs = l.getMap(); 535 setLoggers = true; 536 if (l.getRoot() != null) { 537 root = l.getRoot(); 538 setRoot = true; 539 } 540 } else if (child.getName().equalsIgnoreCase("CustomLevels")) { 541 customLevels = child.getObject(CustomLevels.class).getCustomLevels(); 542 } else if (child.isInstanceOf(CustomLevelConfig.class)) { 543 final List<CustomLevelConfig> copy = new ArrayList<>(customLevels); 544 copy.add(child.getObject(CustomLevelConfig.class)); 545 customLevels = copy; 546 } else { 547 final List<String> expected = Arrays.asList("\"Appenders\"", "\"Loggers\"", "\"Properties\"", 548 "\"Scripts\"", "\"CustomLevels\""); 549 LOGGER.error("Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.", 550 child.getName(), child.getObject().getClass().getName(), expected); 551 } 552 } 553 554 if (!setLoggers) { 555 LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?"); 556 setToDefault(); 557 return; 558 } else if (!setRoot) { 559 LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender"); 560 setToDefault(); 561 // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers 562 } 563 564 for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) { 565 final LoggerConfig loggerConfig = entry.getValue(); 566 for (final AppenderRef ref : loggerConfig.getAppenderRefs()) { 567 final Appender app = appenders.get(ref.getRef()); 568 if (app != null) { 569 loggerConfig.addAppender(app, ref.getLevel(), ref.getFilter()); 570 } else { 571 LOGGER.error("Unable to locate appender \"{}\" for logger config \"{}\"", ref.getRef(), 572 loggerConfig); 573 } 574 } 575 576 } 577 578 setParents(); 579 } 580 581 protected void setToDefault() { 582 // LOG4J2-1176 facilitate memory leak investigation 583 setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode())); 584 final Layout<? extends Serializable> layout = PatternLayout.newBuilder() 585 .withPattern(DefaultConfiguration.DEFAULT_PATTERN) 586 .withConfiguration(this) 587 .build(); 588 final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout); 589 appender.start(); 590 addAppender(appender); 591 final LoggerConfig rootLoggerConfig = getRootLogger(); 592 rootLoggerConfig.addAppender(appender, null, null); 593 594 final Level defaultLevel = Level.ERROR; 595 final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL, 596 defaultLevel.name()); 597 final Level level = Level.valueOf(levelName); 598 rootLoggerConfig.setLevel(level != null ? level : defaultLevel); 599 } 600 601 /** 602 * Set the name of the configuration. 603 * 604 * @param name The name. 605 */ 606 public void setName(final String name) { 607 this.name = name; 608 } 609 610 /** 611 * Returns the name of the configuration. 612 * 613 * @return the name of the configuration. 614 */ 615 @Override 616 public String getName() { 617 return name; 618 } 619 620 /** 621 * Add a listener for changes on the configuration. 622 * 623 * @param listener The ConfigurationListener to add. 624 */ 625 @Override 626 public void addListener(final ConfigurationListener listener) { 627 listeners.add(listener); 628 } 629 630 /** 631 * Remove a ConfigurationListener. 632 * 633 * @param listener The ConfigurationListener to remove. 634 */ 635 @Override 636 public void removeListener(final ConfigurationListener listener) { 637 listeners.remove(listener); 638 } 639 640 /** 641 * Returns the Appender with the specified name. 642 * 643 * @param appenderName The name of the Appender. 644 * @return the Appender with the specified name or null if the Appender cannot be located. 645 */ 646 @Override 647 @SuppressWarnings("unchecked") 648 public <T extends Appender> T getAppender(final String appenderName) { 649 return appenderName != null ? (T) appenders.get(appenderName) : null; 650 } 651 652 /** 653 * Returns a Map containing all the Appenders and their name. 654 * 655 * @return A Map containing each Appender's name and the Appender object. 656 */ 657 @Override 658 public Map<String, Appender> getAppenders() { 659 return appenders; 660 } 661 662 /** 663 * Adds an Appender to the configuration. 664 * 665 * @param appender The Appender to add. 666 */ 667 @Override 668 public void addAppender(final Appender appender) { 669 if (appender != null) { 670 appenders.putIfAbsent(appender.getName(), appender); 671 } 672 } 673 674 @Override 675 public StrSubstitutor getStrSubstitutor() { 676 return subst; 677 } 678 679 @Override 680 public void setAdvertiser(final Advertiser advertiser) { 681 this.advertiser = advertiser; 682 } 683 684 @Override 685 public Advertiser getAdvertiser() { 686 return advertiser; 687 } 688 689 /* 690 * (non-Javadoc) 691 * 692 * @see org.apache.logging.log4j.core.config.ReliabilityStrategyFactory#getReliabilityStrategy(org.apache.logging.log4j 693 * .core.config.LoggerConfig) 694 */ 695 @Override 696 public ReliabilityStrategy getReliabilityStrategy(final LoggerConfig loggerConfig) { 697 return ReliabilityStrategyFactory.getReliabilityStrategy(loggerConfig); 698 } 699 700 /** 701 * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the same name is 702 * being updated at the same time. 703 * 704 * Note: This method is not used when configuring via configuration. It is primarily used by unit tests. 705 * 706 * @param logger The Logger the Appender will be associated with. 707 * @param appender The Appender. 708 */ 709 @Override 710 public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger, 711 final Appender appender) { 712 if (appender == null || logger == null) { 713 return; 714 } 715 final String loggerName = logger.getName(); 716 appenders.putIfAbsent(appender.getName(), appender); 717 final LoggerConfig lc = getLoggerConfig(loggerName); 718 if (lc.getName().equals(loggerName)) { 719 lc.addAppender(appender, null, null); 720 } else { 721 final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive()); 722 nlc.addAppender(appender, null, null); 723 nlc.setParent(lc); 724 loggerConfigs.putIfAbsent(loggerName, nlc); 725 setParents(); 726 logger.getContext().updateLoggers(); 727 } 728 } 729 730 /** 731 * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the same name is being 732 * updated at the same time. 733 * 734 * Note: This method is not used when configuring via configuration. It is primarily used by unit tests. 735 * 736 * @param logger The Logger the Footer will be associated with. 737 * @param filter The Filter. 738 */ 739 @Override 740 public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) { 741 final String loggerName = logger.getName(); 742 final LoggerConfig lc = getLoggerConfig(loggerName); 743 if (lc.getName().equals(loggerName)) { 744 lc.addFilter(filter); 745 } else { 746 final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive()); 747 nlc.addFilter(filter); 748 nlc.setParent(lc); 749 loggerConfigs.putIfAbsent(loggerName, nlc); 750 setParents(); 751 logger.getContext().updateLoggers(); 752 } 753 } 754 755 /** 756 * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the same name is being 757 * updated at the same time. 758 * 759 * Note: This method is not used when configuring via configuration. It is primarily used by unit tests. 760 * 761 * @param logger The Logger the Appender will be associated with. 762 * @param additive True if the LoggerConfig should be additive, false otherwise. 763 */ 764 @Override 765 public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger, final boolean additive) { 766 final String loggerName = logger.getName(); 767 final LoggerConfig lc = getLoggerConfig(loggerName); 768 if (lc.getName().equals(loggerName)) { 769 lc.setAdditive(additive); 770 } else { 771 final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), additive); 772 nlc.setParent(lc); 773 loggerConfigs.putIfAbsent(loggerName, nlc); 774 setParents(); 775 logger.getContext().updateLoggers(); 776 } 777 } 778 779 /** 780 * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes the Appender 781 * from this appender list and then stops the appender. This method is synchronized in case an Appender with the 782 * same name is being added during the removal. 783 * 784 * @param appenderName the name of the appender to remove. 785 */ 786 public synchronized void removeAppender(final String appenderName) { 787 for (final LoggerConfig logger : loggerConfigs.values()) { 788 logger.removeAppender(appenderName); 789 } 790 final Appender app = appenderName != null ? appenders.remove(appenderName) : null; 791 792 if (app != null) { 793 app.stop(); 794 } 795 } 796 797 /* 798 * (non-Javadoc) 799 * 800 * @see org.apache.logging.log4j.core.config.Configuration#getCustomLevels() 801 */ 802 @Override 803 public List<CustomLevelConfig> getCustomLevels() { 804 return Collections.unmodifiableList(customLevels); 805 } 806 807 /** 808 * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the package name as 809 * necessary or return the root LoggerConfig if no other matches were found. 810 * 811 * @param loggerName The Logger name. 812 * @return The located LoggerConfig. 813 */ 814 @Override 815 public LoggerConfig getLoggerConfig(final String loggerName) { 816 LoggerConfig loggerConfig = loggerConfigs.get(loggerName); 817 if (loggerConfig != null) { 818 return loggerConfig; 819 } 820 String substr = loggerName; 821 while ((substr = NameUtil.getSubName(substr)) != null) { 822 loggerConfig = loggerConfigs.get(substr); 823 if (loggerConfig != null) { 824 return loggerConfig; 825 } 826 } 827 return root; 828 } 829 830 @Override 831 public LoggerContext getLoggerContext() { 832 return loggerContext.get(); 833 } 834 835 /** 836 * Returns the root Logger. 837 * 838 * @return the root Logger. 839 */ 840 @Override 841 public LoggerConfig getRootLogger() { 842 return root; 843 } 844 845 /** 846 * Returns a Map of all the LoggerConfigs. 847 * 848 * @return a Map with each entry containing the name of the Logger and the LoggerConfig. 849 */ 850 @Override 851 public Map<String, LoggerConfig> getLoggers() { 852 return Collections.unmodifiableMap(loggerConfigs); 853 } 854 855 /** 856 * Returns the LoggerConfig with the specified name. 857 * 858 * @param loggerName The Logger name. 859 * @return The LoggerConfig or null if no match was found. 860 */ 861 public LoggerConfig getLogger(final String loggerName) { 862 return loggerConfigs.get(loggerName); 863 } 864 865 /** 866 * Add a loggerConfig. The LoggerConfig must already be configured with Appenders, Filters, etc. After addLogger is 867 * called LoggerContext.updateLoggers must be called. 868 * 869 * @param loggerName The name of the Logger. 870 * @param loggerConfig The LoggerConfig. 871 */ 872 @Override 873 public synchronized void addLogger(final String loggerName, final LoggerConfig loggerConfig) { 874 loggerConfigs.putIfAbsent(loggerName, loggerConfig); 875 setParents(); 876 } 877 878 /** 879 * Remove a LoggerConfig. 880 * 881 * @param loggerName The name of the Logger. 882 */ 883 @Override 884 public synchronized void removeLogger(final String loggerName) { 885 loggerConfigs.remove(loggerName); 886 setParents(); 887 } 888 889 @Override 890 public void createConfiguration(final Node node, final LogEvent event) { 891 final PluginType<?> type = node.getType(); 892 if (type != null && type.isDeferChildren()) { 893 node.setObject(createPluginObject(type, node, event)); 894 } else { 895 for (final Node child : node.getChildren()) { 896 createConfiguration(child, event); 897 } 898 899 if (type == null) { 900 if (node.getParent() != null) { 901 LOGGER.error("Unable to locate plugin for {}", node.getName()); 902 } 903 } else { 904 node.setObject(createPluginObject(type, node, event)); 905 } 906 } 907 } 908 909 /** 910 * Invokes a static factory method to either create the desired object or to create a builder object that creates 911 * the desired object. In the case of a factory method, it should be annotated with 912 * {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}, and each parameter should be annotated with 913 * an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with 914 * {@link org.apache.logging.log4j.core.config.plugins.PluginAttribute} must be a type that can be converted from a 915 * string using one of the {@link org.apache.logging.log4j.core.config.plugins.convert.TypeConverter TypeConverters} 916 * . Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or 917 * an array of a plugin class. Collections and Maps are currently not supported, although the factory method that is 918 * called can create these from an array. 919 * 920 * Plugins can also be created using a builder class that implements 921 * {@link org.apache.logging.log4j.core.util.Builder}. In that case, a static method annotated with 922 * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} should create the builder class, and 923 * the various fields in the builder class should be annotated similarly to the method parameters. However, instead 924 * of using PluginAttribute, one should use 925 * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be 926 * specified as the default field value instead of as an additional annotation parameter. 927 * 928 * In either case, there are also annotations for specifying a 929 * {@link org.apache.logging.log4j.core.config.Configuration} ( 930 * {@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a 931 * {@link org.apache.logging.log4j.core.config.Node} ( 932 * {@link org.apache.logging.log4j.core.config.plugins.PluginNode}). 933 * 934 * Although the happy path works, more work still needs to be done to log incorrect parameters. These will generally 935 * result in unhelpful InvocationTargetExceptions. 936 * 937 * @param type the type of plugin to create. 938 * @param node the corresponding configuration node for this plugin to create. 939 * @param event the LogEvent that spurred the creation of this plugin 940 * @return the created plugin object or {@code null} if there was an error setting it up. 941 * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder 942 * @see org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor 943 * @see org.apache.logging.log4j.core.config.plugins.convert.TypeConverter 944 */ 945 private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) { 946 final Class<?> clazz = type.getPluginClass(); 947 948 if (Map.class.isAssignableFrom(clazz)) { 949 try { 950 return createPluginMap(node); 951 } catch (final Exception e) { 952 LOGGER.warn("Unable to create Map for {} of class {}", type.getElementName(), clazz, e); 953 } 954 } 955 956 if (Collection.class.isAssignableFrom(clazz)) { 957 try { 958 return createPluginCollection(node); 959 } catch (final Exception e) { 960 LOGGER.warn("Unable to create List for {} of class {}", type.getElementName(), clazz, e); 961 } 962 } 963 964 return new PluginBuilder(type).withConfiguration(this).withConfigurationNode(node).forLogEvent(event).build(); 965 } 966 967 private static Map<String, ?> createPluginMap(final Node node) { 968 final Map<String, Object> map = new LinkedHashMap<>(); 969 for (final Node child : node.getChildren()) { 970 final Object object = child.getObject(); 971 map.put(child.getName(), object); 972 } 973 return map; 974 } 975 976 private static Collection<?> createPluginCollection(final Node node) { 977 final List<Node> children = node.getChildren(); 978 final Collection<Object> list = new ArrayList<>(children.size()); 979 for (final Node child : children) { 980 final Object object = child.getObject(); 981 list.add(object); 982 } 983 return list; 984 } 985 986 private void setParents() { 987 for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) { 988 final LoggerConfig logger = entry.getValue(); 989 String key = entry.getKey(); 990 if (!key.isEmpty()) { 991 final int i = key.lastIndexOf('.'); 992 if (i > 0) { 993 key = key.substring(0, i); 994 LoggerConfig parent = getLoggerConfig(key); 995 if (parent == null) { 996 parent = root; 997 } 998 logger.setParent(parent); 999 } else { 1000 logger.setParent(root); 1001 } 1002 } 1003 } 1004 } 1005 1006 /** 1007 * Reads an InputStream using buffered reads into a byte array buffer. The given InputStream will remain open after 1008 * invocation of this method. 1009 * 1010 * @param is the InputStream to read into a byte array buffer. 1011 * @return a byte array of the InputStream contents. 1012 * @throws IOException if the {@code read} method of the provided InputStream throws this exception. 1013 */ 1014 protected static byte[] toByteArray(final InputStream is) throws IOException { 1015 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 1016 1017 int nRead; 1018 final byte[] data = new byte[BUF_SIZE]; 1019 1020 while ((nRead = is.read(data, 0, data.length)) != -1) { 1021 buffer.write(data, 0, nRead); 1022 } 1023 1024 return buffer.toByteArray(); 1025 } 1026 1027 @Override 1028 public NanoClock getNanoClock() { 1029 return nanoClock; 1030 } 1031 1032 @Override 1033 public void setNanoClock(final NanoClock nanoClock) { 1034 this.nanoClock = Objects.requireNonNull(nanoClock, "nanoClock"); 1035 } 1036}