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.lang.ref.WeakReference; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.HashSet; 029import java.util.LinkedHashMap; 030import java.util.List; 031import java.util.Map; 032import java.util.Objects; 033import java.util.Set; 034import java.util.concurrent.ConcurrentHashMap; 035import java.util.concurrent.ConcurrentMap; 036import java.util.concurrent.CopyOnWriteArrayList; 037import java.util.concurrent.TimeUnit; 038 039import org.apache.logging.log4j.Level; 040import org.apache.logging.log4j.core.Appender; 041import org.apache.logging.log4j.core.Filter; 042import org.apache.logging.log4j.core.Layout; 043import org.apache.logging.log4j.core.LifeCycle2; 044import org.apache.logging.log4j.core.LogEvent; 045import org.apache.logging.log4j.core.LoggerContext; 046import org.apache.logging.log4j.core.Version; 047import org.apache.logging.log4j.core.appender.AsyncAppender; 048import org.apache.logging.log4j.core.appender.ConsoleAppender; 049import org.apache.logging.log4j.core.async.AsyncLoggerConfig; 050import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate; 051import org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor; 052import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder; 053import org.apache.logging.log4j.core.config.plugins.util.PluginManager; 054import org.apache.logging.log4j.core.config.plugins.util.PluginType; 055import org.apache.logging.log4j.core.filter.AbstractFilterable; 056import org.apache.logging.log4j.core.layout.PatternLayout; 057import org.apache.logging.log4j.core.lookup.ConfigurationStrSubstitutor; 058import org.apache.logging.log4j.core.lookup.Interpolator; 059import org.apache.logging.log4j.core.lookup.MapLookup; 060import org.apache.logging.log4j.core.lookup.RuntimeStrSubstitutor; 061import org.apache.logging.log4j.core.lookup.StrLookup; 062import org.apache.logging.log4j.core.lookup.StrSubstitutor; 063import org.apache.logging.log4j.core.net.Advertiser; 064import org.apache.logging.log4j.core.script.AbstractScript; 065import org.apache.logging.log4j.core.script.ScriptManager; 066import org.apache.logging.log4j.core.script.ScriptRef; 067import org.apache.logging.log4j.core.util.Constants; 068import org.apache.logging.log4j.core.util.DummyNanoClock; 069import org.apache.logging.log4j.core.util.Loader; 070import org.apache.logging.log4j.core.util.NameUtil; 071import org.apache.logging.log4j.core.util.NanoClock; 072import org.apache.logging.log4j.core.util.Source; 073import org.apache.logging.log4j.core.util.WatchManager; 074import org.apache.logging.log4j.core.util.Watcher; 075import org.apache.logging.log4j.core.util.WatcherFactory; 076import org.apache.logging.log4j.util.PropertiesUtil; 077 078/** 079 * The base Configuration. Many configuration implementations will extend this class. 080 */ 081public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration { 082 083 private static final int BUF_SIZE = 16384; 084 085 /** 086 * The root node of the configuration. 087 */ 088 protected Node rootNode; 089 090 /** 091 * Listeners for configuration changes. 092 */ 093 protected final List<ConfigurationListener> listeners = new CopyOnWriteArrayList<>(); 094 095 /** 096 * Packages found in configuration "packages" attribute. 097 */ 098 protected final List<String> pluginPackages = new ArrayList<>(); 099 100 /** 101 * The plugin manager. 102 */ 103 protected PluginManager pluginManager; 104 105 /** 106 * Shutdown hook is enabled by default. 107 */ 108 protected boolean isShutdownHookEnabled = true; 109 110 /** 111 * Shutdown timeout in milliseconds. 112 */ 113 protected long shutdownTimeoutMillis = 0; 114 115 /** 116 * The Script manager. 117 */ 118 protected ScriptManager scriptManager; 119 120 /** 121 * The Advertiser which exposes appender configurations to external systems. 122 */ 123 private Advertiser advertiser = new DefaultAdvertiser(); 124 private Node advertiserNode = null; 125 private Object advertisement; 126 private String name; 127 private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<>(); 128 private ConcurrentMap<String, LoggerConfig> loggerConfigs = new ConcurrentHashMap<>(); 129 private List<CustomLevelConfig> customLevels = Collections.emptyList(); 130 private final ConcurrentMap<String, String> propertyMap = new ConcurrentHashMap<>(); 131 private final StrLookup tempLookup = new Interpolator(propertyMap); 132 private final StrSubstitutor subst = new RuntimeStrSubstitutor(tempLookup); 133 private final StrSubstitutor configurationStrSubstitutor = new ConfigurationStrSubstitutor(subst); 134 private LoggerConfig root = new LoggerConfig(); 135 private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<>(); 136 private final ConfigurationSource configurationSource; 137 private final ConfigurationScheduler configurationScheduler = new ConfigurationScheduler(); 138 private final WatchManager watchManager = new WatchManager(configurationScheduler); 139 private AsyncLoggerConfigDisruptor asyncLoggerConfigDisruptor; 140 private NanoClock nanoClock = new DummyNanoClock(); 141 private final WeakReference<LoggerContext> loggerContext; 142 143 /** 144 * Constructor. 145 */ 146 protected AbstractConfiguration(final LoggerContext loggerContext, final ConfigurationSource configurationSource) { 147 this.loggerContext = new WeakReference<>(loggerContext); 148 // The loggerContext is null for the NullConfiguration class. 149 // this.loggerContext = new WeakReference(Objects.requireNonNull(loggerContext, "loggerContext is null")); 150 this.configurationSource = Objects.requireNonNull(configurationSource, "configurationSource is null"); 151 componentMap.put(Configuration.CONTEXT_PROPERTIES, propertyMap); 152 pluginManager = new PluginManager(Node.CATEGORY); 153 rootNode = new Node(); 154 setState(State.INITIALIZING); 155 156 } 157 158 @Override 159 public ConfigurationSource getConfigurationSource() { 160 return configurationSource; 161 } 162 163 @Override 164 public List<String> getPluginPackages() { 165 return pluginPackages; 166 } 167 168 @Override 169 public Map<String, String> getProperties() { 170 return propertyMap; 171 } 172 173 @Override 174 public ScriptManager getScriptManager() { 175 return scriptManager; 176 } 177 178 public void setScriptManager(final ScriptManager scriptManager) { 179 this.scriptManager = scriptManager; 180 } 181 182 public PluginManager getPluginManager() { 183 return pluginManager; 184 } 185 186 public void setPluginManager(final PluginManager pluginManager) { 187 this.pluginManager = pluginManager; 188 } 189 190 @Override 191 public WatchManager getWatchManager() { 192 return watchManager; 193 } 194 195 @Override 196 public ConfigurationScheduler getScheduler() { 197 return configurationScheduler; 198 } 199 200 public Node getRootNode() { 201 return rootNode; 202 } 203 204 @Override 205 public AsyncLoggerConfigDelegate getAsyncLoggerConfigDelegate() { 206 // lazily instantiate only when requested by AsyncLoggers: 207 // loading AsyncLoggerConfigDisruptor requires LMAX Disruptor jar on classpath 208 if (asyncLoggerConfigDisruptor == null) { 209 asyncLoggerConfigDisruptor = new AsyncLoggerConfigDisruptor(); 210 } 211 return asyncLoggerConfigDisruptor; 212 } 213 214 /** 215 * Initialize the configuration. 216 */ 217 @Override 218 public void initialize() { 219 LOGGER.debug(Version.getProductString() + " initializing configuration {}", this); 220 subst.setConfiguration(this); 221 configurationStrSubstitutor.setConfiguration(this); 222 try { 223 scriptManager = new ScriptManager(this, watchManager); 224 } catch (final LinkageError | Exception e) { 225 // LOG4J2-1920 ScriptEngineManager is not available in Android 226 LOGGER.info("Cannot initialize scripting support because this JRE does not support it.", e); 227 } 228 pluginManager.collectPlugins(pluginPackages); 229 final PluginManager levelPlugins = new PluginManager(Level.CATEGORY); 230 levelPlugins.collectPlugins(pluginPackages); 231 final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins(); 232 if (plugins != null) { 233 for (final PluginType<?> type : plugins.values()) { 234 try { 235 // Cause the class to be initialized if it isn't already. 236 Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader()); 237 } catch (final Exception e) { 238 LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass() 239 .getSimpleName(), e); 240 } 241 } 242 } 243 setup(); 244 setupAdvertisement(); 245 doConfigure(); 246 setState(State.INITIALIZED); 247 LOGGER.debug("Configuration {} initialized", this); 248 } 249 250 protected void initializeWatchers(Reconfigurable reconfigurable, ConfigurationSource configSource, 251 int monitorIntervalSeconds) { 252 if (configSource.getFile() != null || configSource.getURL() != null) { 253 if (monitorIntervalSeconds > 0) { 254 watchManager.setIntervalSeconds(monitorIntervalSeconds); 255 if (configSource.getFile() != null) { 256 final Source cfgSource = new Source(configSource); 257 final long lastModifeid = configSource.getFile().lastModified(); 258 final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher(this, reconfigurable, 259 listeners, lastModifeid); 260 watchManager.watch(cfgSource, watcher); 261 } else { 262 if (configSource.getURL() != null) { 263 monitorSource(reconfigurable, configSource); 264 } 265 } 266 } else if (watchManager.hasEventListeners() && configSource.getURL() != null && monitorIntervalSeconds >= 0) { 267 monitorSource(reconfigurable, configSource); 268 } 269 } 270 } 271 272 private void monitorSource(Reconfigurable reconfigurable, ConfigurationSource configSource) { 273 if (configSource.getLastModified() > 0) { 274 final Source cfgSource = new Source(configSource); 275 final Watcher watcher = WatcherFactory.getInstance(pluginPackages) 276 .newWatcher(cfgSource, this, reconfigurable, listeners, configSource.getLastModified()); 277 if (watcher != null) { 278 watchManager.watch(cfgSource, watcher); 279 } 280 } else { 281 LOGGER.info("{} does not support dynamic reconfiguration", configSource.getURI()); 282 } 283 } 284 285 /** 286 * Start the configuration. 287 */ 288 @Override 289 public void start() { 290 // Preserve the prior behavior of initializing during start if not initialized. 291 if (getState().equals(State.INITIALIZING)) { 292 initialize(); 293 } 294 LOGGER.debug("Starting configuration {}", this); 295 this.setStarting(); 296 if (watchManager.getIntervalSeconds() >= 0) { 297 watchManager.start(); 298 } 299 if (hasAsyncLoggers()) { 300 asyncLoggerConfigDisruptor.start(); 301 } 302 final Set<LoggerConfig> alreadyStarted = new HashSet<>(); 303 for (final LoggerConfig logger : loggerConfigs.values()) { 304 logger.start(); 305 alreadyStarted.add(logger); 306 } 307 for (final Appender appender : appenders.values()) { 308 appender.start(); 309 } 310 if (!alreadyStarted.contains(root)) { // LOG4J2-392 311 root.start(); // LOG4J2-336 312 } 313 super.start(); 314 LOGGER.debug("Started configuration {} OK.", this); 315 } 316 317 private boolean hasAsyncLoggers() { 318 if (root instanceof AsyncLoggerConfig) { 319 return true; 320 } 321 for (final LoggerConfig logger : loggerConfigs.values()) { 322 if (logger instanceof AsyncLoggerConfig) { 323 return true; 324 } 325 } 326 return false; 327 } 328 329 /** 330 * Tear down the configuration. 331 */ 332 @Override 333 public boolean stop(final long timeout, final TimeUnit timeUnit) { 334 this.setStopping(); 335 super.stop(timeout, timeUnit, false); 336 LOGGER.trace("Stopping {}...", this); 337 338 // Stop the components that are closest to the application first: 339 // 1. Notify all LoggerConfigs' ReliabilityStrategy that the configuration will be stopped. 340 // 2. Stop the LoggerConfig objects (this may stop nested Filters) 341 // 3. Stop the AsyncLoggerConfigDelegate. This shuts down the AsyncLoggerConfig Disruptor 342 // and waits until all events in the RingBuffer have been processed. 343 // 4. Stop all AsyncAppenders. This shuts down the associated thread and 344 // waits until all events in the queue have been processed. (With optional timeout.) 345 // 5. Notify all LoggerConfigs' ReliabilityStrategy that appenders will be stopped. 346 // This guarantees that any event received by a LoggerConfig before reconfiguration 347 // are passed on to the Appenders before the Appenders are stopped. 348 // 6. Stop the remaining running Appenders. (It should now be safe to do so.) 349 // 7. Notify all LoggerConfigs that their Appenders can be cleaned up. 350 351 for (final LoggerConfig loggerConfig : loggerConfigs.values()) { 352 loggerConfig.getReliabilityStrategy().beforeStopConfiguration(this); 353 } 354 root.getReliabilityStrategy().beforeStopConfiguration(this); 355 356 final String cls = getClass().getSimpleName(); 357 LOGGER.trace("{} notified {} ReliabilityStrategies that config will be stopped.", cls, loggerConfigs.size() 358 + 1); 359 360 if (!loggerConfigs.isEmpty()) { 361 LOGGER.trace("{} stopping {} LoggerConfigs.", cls, loggerConfigs.size()); 362 for (final LoggerConfig logger : loggerConfigs.values()) { 363 logger.stop(timeout, timeUnit); 364 } 365 } 366 LOGGER.trace("{} stopping root LoggerConfig.", cls); 367 if (!root.isStopped()) { 368 root.stop(timeout, timeUnit); 369 } 370 371 if (hasAsyncLoggers()) { 372 LOGGER.trace("{} stopping AsyncLoggerConfigDisruptor.", cls); 373 asyncLoggerConfigDisruptor.stop(timeout, timeUnit); 374 } 375 376 // Stop the appenders in reverse order in case they still have activity. 377 final Appender[] array = appenders.values().toArray(new Appender[appenders.size()]); 378 final List<Appender> async = getAsyncAppenders(array); 379 if (!async.isEmpty()) { 380 // LOG4J2-511, LOG4J2-392 stop AsyncAppenders first 381 LOGGER.trace("{} stopping {} AsyncAppenders.", cls, async.size()); 382 for (final Appender appender : async) { 383 if (appender instanceof LifeCycle2) { 384 ((LifeCycle2) appender).stop(timeout, timeUnit); 385 } else { 386 appender.stop(); 387 } 388 } 389 } 390 391 LOGGER.trace("{} notifying ReliabilityStrategies that appenders will be stopped.", cls); 392 for (final LoggerConfig loggerConfig : loggerConfigs.values()) { 393 loggerConfig.getReliabilityStrategy().beforeStopAppenders(); 394 } 395 root.getReliabilityStrategy().beforeStopAppenders(); 396 397 LOGGER.trace("{} stopping remaining Appenders.", cls); 398 int appenderCount = 0; 399 for (int i = array.length - 1; i >= 0; --i) { 400 if (array[i].isStarted()) { // then stop remaining Appenders 401 if (array[i] instanceof LifeCycle2) { 402 ((LifeCycle2) array[i]).stop(timeout, timeUnit); 403 } else { 404 array[i].stop(); 405 } 406 appenderCount++; 407 } 408 } 409 LOGGER.trace("{} stopped {} remaining Appenders.", cls, appenderCount); 410 411 LOGGER.trace("{} cleaning Appenders from {} LoggerConfigs.", cls, loggerConfigs.size() + 1); 412 for (final LoggerConfig loggerConfig : loggerConfigs.values()) { 413 414 // LOG4J2-520, LOG4J2-392: 415 // Important: do not clear appenders until after all AsyncLoggerConfigs 416 // have been stopped! Stopping the last AsyncLoggerConfig will 417 // shut down the disruptor and wait for all enqueued events to be processed. 418 // Only *after this* the appenders can be cleared or events will be lost. 419 loggerConfig.clearAppenders(); 420 } 421 root.clearAppenders(); 422 423 if (watchManager.isStarted()) { 424 watchManager.stop(timeout, timeUnit); 425 } 426 configurationScheduler.stop(timeout, timeUnit); 427 428 if (advertiser != null && advertisement != null) { 429 advertiser.unadvertise(advertisement); 430 } 431 setStopped(); 432 LOGGER.debug("Stopped {} OK", this); 433 return true; 434 } 435 436 private List<Appender> getAsyncAppenders(final Appender[] all) { 437 final List<Appender> result = new ArrayList<>(); 438 for (int i = all.length - 1; i >= 0; --i) { 439 if (all[i] instanceof AsyncAppender) { 440 result.add(all[i]); 441 } 442 } 443 return result; 444 } 445 446 @Override 447 public boolean isShutdownHookEnabled() { 448 return isShutdownHookEnabled; 449 } 450 451 @Override 452 public long getShutdownTimeoutMillis() { 453 return shutdownTimeoutMillis; 454 } 455 456 public void setup() { 457 // default does nothing, subclasses do work. 458 } 459 460 protected Level getDefaultStatus() { 461 final String statusLevel = PropertiesUtil.getProperties().getStringProperty( 462 Constants.LOG4J_DEFAULT_STATUS_LEVEL, Level.ERROR.name()); 463 try { 464 return Level.toLevel(statusLevel); 465 } catch (final Exception ex) { 466 return Level.ERROR; 467 } 468 } 469 470 protected void createAdvertiser(final String advertiserString, final ConfigurationSource configSource, 471 final byte[] buffer, final String contentType) { 472 if (advertiserString != null) { 473 final Node node = new Node(null, advertiserString, null); 474 final Map<String, String> attributes = node.getAttributes(); 475 attributes.put("content", new String(buffer)); 476 attributes.put("contentType", contentType); 477 attributes.put("name", "configuration"); 478 if (configSource.getLocation() != null) { 479 attributes.put("location", configSource.getLocation()); 480 } 481 advertiserNode = node; 482 } 483 } 484 485 private void setupAdvertisement() { 486 if (advertiserNode != null) { 487 final String nodeName = advertiserNode.getName(); 488 final PluginType<?> type = pluginManager.getPluginType(nodeName); 489 if (type != null) { 490 final Class<? extends Advertiser> clazz = type.getPluginClass().asSubclass(Advertiser.class); 491 try { 492 advertiser = clazz.newInstance(); 493 advertisement = advertiser.advertise(advertiserNode.getAttributes()); 494 } catch (final InstantiationException e) { 495 LOGGER.error("InstantiationException attempting to instantiate advertiser: {}", nodeName, e); 496 } catch (final IllegalAccessException e) { 497 LOGGER.error("IllegalAccessException attempting to instantiate advertiser: {}", nodeName, e); 498 } 499 } 500 } 501 } 502 503 @SuppressWarnings("unchecked") 504 @Override 505 public <T> T getComponent(final String componentName) { 506 return (T) componentMap.get(componentName); 507 } 508 509 @Override 510 public void addComponent(final String componentName, final Object obj) { 511 componentMap.putIfAbsent(componentName, obj); 512 } 513 514 protected void preConfigure(final Node node) { 515 try { 516 for (final Node child : node.getChildren()) { 517 if (child.getType() == null) { 518 LOGGER.error("Unable to locate plugin type for " + child.getName()); 519 continue; 520 } 521 final Class<?> clazz = child.getType().getPluginClass(); 522 if (clazz.isAnnotationPresent(Scheduled.class)) { 523 configurationScheduler.incrementScheduledItems(); 524 } 525 preConfigure(child); 526 } 527 } catch (final Exception ex) { 528 LOGGER.error("Error capturing node data for node " + node.getName(), ex); 529 } 530 } 531 532 protected void doConfigure() { 533 preConfigure(rootNode); 534 configurationScheduler.start(); 535 if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) { 536 final Node first = rootNode.getChildren().get(0); 537 createConfiguration(first, null); 538 if (first.getObject() != null) { 539 StrLookup lookup = (StrLookup) first.getObject(); 540 subst.setVariableResolver(lookup); 541 configurationStrSubstitutor.setVariableResolver(lookup); 542 } 543 } else { 544 final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES); 545 final StrLookup lookup = map == null ? null : new MapLookup(map); 546 Interpolator interpolator = new Interpolator(lookup, pluginPackages); 547 subst.setVariableResolver(interpolator); 548 configurationStrSubstitutor.setVariableResolver(interpolator); 549 } 550 551 boolean setLoggers = false; 552 boolean setRoot = false; 553 for (final Node child : rootNode.getChildren()) { 554 if (child.getName().equalsIgnoreCase("Properties")) { 555 if (tempLookup == subst.getVariableResolver()) { 556 LOGGER.error("Properties declaration must be the first element in the configuration"); 557 } 558 continue; 559 } 560 createConfiguration(child, null); 561 if (child.getObject() == null) { 562 continue; 563 } 564 if (child.getName().equalsIgnoreCase("Scripts")) { 565 for (final AbstractScript script : child.getObject(AbstractScript[].class)) { 566 if (script instanceof ScriptRef) { 567 LOGGER.error("Script reference to {} not added. Scripts definition cannot contain script references", 568 script.getName()); 569 } else { 570 if (scriptManager != null) { 571 scriptManager.addScript(script); 572 }} 573 } 574 } else if (child.getName().equalsIgnoreCase("Appenders")) { 575 appenders = child.getObject(); 576 } else if (child.isInstanceOf(Filter.class)) { 577 addFilter(child.getObject(Filter.class)); 578 } else if (child.getName().equalsIgnoreCase("Loggers")) { 579 final Loggers l = child.getObject(); 580 loggerConfigs = l.getMap(); 581 setLoggers = true; 582 if (l.getRoot() != null) { 583 root = l.getRoot(); 584 setRoot = true; 585 } 586 } else if (child.getName().equalsIgnoreCase("CustomLevels")) { 587 customLevels = child.getObject(CustomLevels.class).getCustomLevels(); 588 } else if (child.isInstanceOf(CustomLevelConfig.class)) { 589 final List<CustomLevelConfig> copy = new ArrayList<>(customLevels); 590 copy.add(child.getObject(CustomLevelConfig.class)); 591 customLevels = copy; 592 } else { 593 final List<String> expected = Arrays.asList("\"Appenders\"", "\"Loggers\"", "\"Properties\"", 594 "\"Scripts\"", "\"CustomLevels\""); 595 LOGGER.error("Unknown object \"{}\" of type {} is ignored: try nesting it inside one of: {}.", 596 child.getName(), child.getObject().getClass().getName(), expected); 597 } 598 } 599 600 if (!setLoggers) { 601 LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?"); 602 setToDefault(); 603 return; 604 } else if (!setRoot) { 605 LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender"); 606 setToDefault(); 607 // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers 608 } 609 610 for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) { 611 final LoggerConfig loggerConfig = entry.getValue(); 612 for (final AppenderRef ref : loggerConfig.getAppenderRefs()) { 613 final Appender app = appenders.get(ref.getRef()); 614 if (app != null) { 615 loggerConfig.addAppender(app, ref.getLevel(), ref.getFilter()); 616 } else { 617 LOGGER.error("Unable to locate appender \"{}\" for logger config \"{}\"", ref.getRef(), 618 loggerConfig); 619 } 620 } 621 622 } 623 624 setParents(); 625 } 626 627 protected void setToDefault() { 628 // LOG4J2-1176 facilitate memory leak investigation 629 setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode())); 630 final Layout<? extends Serializable> layout = PatternLayout.newBuilder() 631 .withPattern(DefaultConfiguration.DEFAULT_PATTERN) 632 .withConfiguration(this) 633 .build(); 634 final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout); 635 appender.start(); 636 addAppender(appender); 637 final LoggerConfig rootLoggerConfig = getRootLogger(); 638 rootLoggerConfig.addAppender(appender, null, null); 639 640 final Level defaultLevel = Level.ERROR; 641 final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL, 642 defaultLevel.name()); 643 final Level level = Level.valueOf(levelName); 644 rootLoggerConfig.setLevel(level != null ? level : defaultLevel); 645 } 646 647 /** 648 * Set the name of the configuration. 649 * 650 * @param name The name. 651 */ 652 public void setName(final String name) { 653 this.name = name; 654 } 655 656 /** 657 * Returns the name of the configuration. 658 * 659 * @return the name of the configuration. 660 */ 661 @Override 662 public String getName() { 663 return name; 664 } 665 666 /** 667 * Add a listener for changes on the configuration. 668 * 669 * @param listener The ConfigurationListener to add. 670 */ 671 @Override 672 public void addListener(final ConfigurationListener listener) { 673 listeners.add(listener); 674 } 675 676 /** 677 * Remove a ConfigurationListener. 678 * 679 * @param listener The ConfigurationListener to remove. 680 */ 681 @Override 682 public void removeListener(final ConfigurationListener listener) { 683 listeners.remove(listener); 684 } 685 686 /** 687 * Returns the Appender with the specified name. 688 * 689 * @param appenderName The name of the Appender. 690 * @return the Appender with the specified name or null if the Appender cannot be located. 691 */ 692 @Override 693 @SuppressWarnings("unchecked") 694 public <T extends Appender> T getAppender(final String appenderName) { 695 return appenderName != null ? (T) appenders.get(appenderName) : null; 696 } 697 698 /** 699 * Returns a Map containing all the Appenders and their name. 700 * 701 * @return A Map containing each Appender's name and the Appender object. 702 */ 703 @Override 704 public Map<String, Appender> getAppenders() { 705 return appenders; 706 } 707 708 /** 709 * Adds an Appender to the configuration. 710 * 711 * @param appender The Appender to add. 712 */ 713 @Override 714 public void addAppender(final Appender appender) { 715 if (appender != null) { 716 appenders.putIfAbsent(appender.getName(), appender); 717 } 718 } 719 720 @Override 721 public StrSubstitutor getStrSubstitutor() { 722 return subst; 723 } 724 725 public StrSubstitutor getConfigurationStrSubstitutor() { 726 return configurationStrSubstitutor; 727 } 728 729 @Override 730 public void setAdvertiser(final Advertiser advertiser) { 731 this.advertiser = advertiser; 732 } 733 734 @Override 735 public Advertiser getAdvertiser() { 736 return advertiser; 737 } 738 739 /* 740 * (non-Javadoc) 741 * 742 * @see org.apache.logging.log4j.core.config.ReliabilityStrategyFactory#getReliabilityStrategy(org.apache.logging.log4j 743 * .core.config.LoggerConfig) 744 */ 745 @Override 746 public ReliabilityStrategy getReliabilityStrategy(final LoggerConfig loggerConfig) { 747 return ReliabilityStrategyFactory.getReliabilityStrategy(loggerConfig); 748 } 749 750 /** 751 * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the same name is 752 * being updated at the same time. 753 * 754 * Note: This method is not used when configuring via configuration. It is primarily used by unit tests. 755 * 756 * @param logger The Logger the Appender will be associated with. 757 * @param appender The Appender. 758 */ 759 @Override 760 public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger, 761 final Appender appender) { 762 if (appender == null || logger == null) { 763 return; 764 } 765 final String loggerName = logger.getName(); 766 appenders.putIfAbsent(appender.getName(), appender); 767 final LoggerConfig lc = getLoggerConfig(loggerName); 768 if (lc.getName().equals(loggerName)) { 769 lc.addAppender(appender, null, null); 770 } else { 771 final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive()); 772 nlc.addAppender(appender, null, null); 773 nlc.setParent(lc); 774 loggerConfigs.putIfAbsent(loggerName, nlc); 775 setParents(); 776 logger.getContext().updateLoggers(); 777 } 778 } 779 780 /** 781 * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the same name is being 782 * updated at the same time. 783 * 784 * Note: This method is not used when configuring via configuration. It is primarily used by unit tests. 785 * 786 * @param logger The Logger the Footer will be associated with. 787 * @param filter The Filter. 788 */ 789 @Override 790 public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) { 791 final String loggerName = logger.getName(); 792 final LoggerConfig lc = getLoggerConfig(loggerName); 793 if (lc.getName().equals(loggerName)) { 794 lc.addFilter(filter); 795 } else { 796 final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive()); 797 nlc.addFilter(filter); 798 nlc.setParent(lc); 799 loggerConfigs.putIfAbsent(loggerName, nlc); 800 setParents(); 801 logger.getContext().updateLoggers(); 802 } 803 } 804 805 /** 806 * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the same name is being 807 * updated at the same time. 808 * 809 * Note: This method is not used when configuring via configuration. It is primarily used by unit tests. 810 * 811 * @param logger The Logger the Appender will be associated with. 812 * @param additive True if the LoggerConfig should be additive, false otherwise. 813 */ 814 @Override 815 public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger, final boolean additive) { 816 final String loggerName = logger.getName(); 817 final LoggerConfig lc = getLoggerConfig(loggerName); 818 if (lc.getName().equals(loggerName)) { 819 lc.setAdditive(additive); 820 } else { 821 final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), additive); 822 nlc.setParent(lc); 823 loggerConfigs.putIfAbsent(loggerName, nlc); 824 setParents(); 825 logger.getContext().updateLoggers(); 826 } 827 } 828 829 /** 830 * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes the Appender 831 * from this appender list and then stops the appender. This method is synchronized in case an Appender with the 832 * same name is being added during the removal. 833 * 834 * @param appenderName the name of the appender to remove. 835 */ 836 public synchronized void removeAppender(final String appenderName) { 837 for (final LoggerConfig logger : loggerConfigs.values()) { 838 logger.removeAppender(appenderName); 839 } 840 final Appender app = appenderName != null ? appenders.remove(appenderName) : null; 841 842 if (app != null) { 843 app.stop(); 844 } 845 } 846 847 /* 848 * (non-Javadoc) 849 * 850 * @see org.apache.logging.log4j.core.config.Configuration#getCustomLevels() 851 */ 852 @Override 853 public List<CustomLevelConfig> getCustomLevels() { 854 return Collections.unmodifiableList(customLevels); 855 } 856 857 /** 858 * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the package name as 859 * necessary or return the root LoggerConfig if no other matches were found. 860 * 861 * @param loggerName The Logger name. 862 * @return The located LoggerConfig. 863 */ 864 @Override 865 public LoggerConfig getLoggerConfig(final String loggerName) { 866 LoggerConfig loggerConfig = loggerConfigs.get(loggerName); 867 if (loggerConfig != null) { 868 return loggerConfig; 869 } 870 String substr = loggerName; 871 while ((substr = NameUtil.getSubName(substr)) != null) { 872 loggerConfig = loggerConfigs.get(substr); 873 if (loggerConfig != null) { 874 return loggerConfig; 875 } 876 } 877 return root; 878 } 879 880 @Override 881 public LoggerContext getLoggerContext() { 882 return loggerContext.get(); 883 } 884 885 /** 886 * Returns the root Logger. 887 * 888 * @return the root Logger. 889 */ 890 @Override 891 public LoggerConfig getRootLogger() { 892 return root; 893 } 894 895 /** 896 * Returns a Map of all the LoggerConfigs. 897 * 898 * @return a Map with each entry containing the name of the Logger and the LoggerConfig. 899 */ 900 @Override 901 public Map<String, LoggerConfig> getLoggers() { 902 return Collections.unmodifiableMap(loggerConfigs); 903 } 904 905 /** 906 * Returns the LoggerConfig with the specified name. 907 * 908 * @param loggerName The Logger name. 909 * @return The LoggerConfig or null if no match was found. 910 */ 911 public LoggerConfig getLogger(final String loggerName) { 912 return loggerConfigs.get(loggerName); 913 } 914 915 /** 916 * Add a loggerConfig. The LoggerConfig must already be configured with Appenders, Filters, etc. After addLogger is 917 * called LoggerContext.updateLoggers must be called. 918 * 919 * @param loggerName The name of the Logger. 920 * @param loggerConfig The LoggerConfig. 921 */ 922 @Override 923 public synchronized void addLogger(final String loggerName, final LoggerConfig loggerConfig) { 924 loggerConfigs.putIfAbsent(loggerName, loggerConfig); 925 setParents(); 926 } 927 928 /** 929 * Remove a LoggerConfig. 930 * 931 * @param loggerName The name of the Logger. 932 */ 933 @Override 934 public synchronized void removeLogger(final String loggerName) { 935 loggerConfigs.remove(loggerName); 936 setParents(); 937 } 938 939 @Override 940 public void createConfiguration(final Node node, final LogEvent event) { 941 final PluginType<?> type = node.getType(); 942 if (type != null && type.isDeferChildren()) { 943 node.setObject(createPluginObject(type, node, event)); 944 } else { 945 for (final Node child : node.getChildren()) { 946 createConfiguration(child, event); 947 } 948 949 if (type == null) { 950 if (node.getParent() != null) { 951 LOGGER.error("Unable to locate plugin for {}", node.getName()); 952 } 953 } else { 954 node.setObject(createPluginObject(type, node, event)); 955 } 956 } 957 } 958 959 /** 960 * Invokes a static factory method to either create the desired object or to create a builder object that creates 961 * the desired object. In the case of a factory method, it should be annotated with 962 * {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}, and each parameter should be annotated with 963 * an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with 964 * {@link org.apache.logging.log4j.core.config.plugins.PluginAttribute} must be a type that can be converted from a 965 * string using one of the {@link org.apache.logging.log4j.core.config.plugins.convert.TypeConverter TypeConverters} 966 * . Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or 967 * an array of a plugin class. Collections and Maps are currently not supported, although the factory method that is 968 * called can create these from an array. 969 * 970 * Plugins can also be created using a builder class that implements 971 * {@link org.apache.logging.log4j.core.util.Builder}. In that case, a static method annotated with 972 * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} should create the builder class, and 973 * the various fields in the builder class should be annotated similarly to the method parameters. However, instead 974 * of using PluginAttribute, one should use 975 * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be 976 * specified as the default field value instead of as an additional annotation parameter. 977 * 978 * In either case, there are also annotations for specifying a 979 * {@link org.apache.logging.log4j.core.config.Configuration} ( 980 * {@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a 981 * {@link org.apache.logging.log4j.core.config.Node} ( 982 * {@link org.apache.logging.log4j.core.config.plugins.PluginNode}). 983 * 984 * Although the happy path works, more work still needs to be done to log incorrect parameters. These will generally 985 * result in unhelpful InvocationTargetExceptions. 986 * 987 * @param type the type of plugin to create. 988 * @param node the corresponding configuration node for this plugin to create. 989 * @param event the LogEvent that spurred the creation of this plugin 990 * @return the created plugin object or {@code null} if there was an error setting it up. 991 * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder 992 * @see org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor 993 * @see org.apache.logging.log4j.core.config.plugins.convert.TypeConverter 994 */ 995 private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) { 996 final Class<?> clazz = type.getPluginClass(); 997 998 if (Map.class.isAssignableFrom(clazz)) { 999 try { 1000 return createPluginMap(node); 1001 } catch (final Exception e) { 1002 LOGGER.warn("Unable to create Map for {} of class {}", type.getElementName(), clazz, e); 1003 } 1004 } 1005 1006 if (Collection.class.isAssignableFrom(clazz)) { 1007 try { 1008 return createPluginCollection(node); 1009 } catch (final Exception e) { 1010 LOGGER.warn("Unable to create List for {} of class {}", type.getElementName(), clazz, e); 1011 } 1012 } 1013 1014 return new PluginBuilder(type).withConfiguration(this).withConfigurationNode(node).forLogEvent(event).build(); 1015 } 1016 1017 private static Map<String, ?> createPluginMap(final Node node) { 1018 final Map<String, Object> map = new LinkedHashMap<>(); 1019 for (final Node child : node.getChildren()) { 1020 final Object object = child.getObject(); 1021 map.put(child.getName(), object); 1022 } 1023 return map; 1024 } 1025 1026 private static Collection<?> createPluginCollection(final Node node) { 1027 final List<Node> children = node.getChildren(); 1028 final Collection<Object> list = new ArrayList<>(children.size()); 1029 for (final Node child : children) { 1030 final Object object = child.getObject(); 1031 list.add(object); 1032 } 1033 return list; 1034 } 1035 1036 private void setParents() { 1037 for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) { 1038 final LoggerConfig logger = entry.getValue(); 1039 String key = entry.getKey(); 1040 if (!key.isEmpty()) { 1041 final int i = key.lastIndexOf('.'); 1042 if (i > 0) { 1043 key = key.substring(0, i); 1044 LoggerConfig parent = getLoggerConfig(key); 1045 if (parent == null) { 1046 parent = root; 1047 } 1048 logger.setParent(parent); 1049 } else { 1050 logger.setParent(root); 1051 } 1052 } 1053 } 1054 } 1055 1056 /** 1057 * Reads an InputStream using buffered reads into a byte array buffer. The given InputStream will remain open after 1058 * invocation of this method. 1059 * 1060 * @param is the InputStream to read into a byte array buffer. 1061 * @return a byte array of the InputStream contents. 1062 * @throws IOException if the {@code read} method of the provided InputStream throws this exception. 1063 */ 1064 protected static byte[] toByteArray(final InputStream is) throws IOException { 1065 final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 1066 1067 int nRead; 1068 final byte[] data = new byte[BUF_SIZE]; 1069 1070 while ((nRead = is.read(data, 0, data.length)) != -1) { 1071 buffer.write(data, 0, nRead); 1072 } 1073 1074 return buffer.toByteArray(); 1075 } 1076 1077 @Override 1078 public NanoClock getNanoClock() { 1079 return nanoClock; 1080 } 1081 1082 @Override 1083 public void setNanoClock(final NanoClock nanoClock) { 1084 this.nanoClock = Objects.requireNonNull(nanoClock, "nanoClock"); 1085 } 1086}