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