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