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