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