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