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