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