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