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