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