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