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> properties = new ConcurrentHashMap<>();
126     private final StrLookup tempLookup = new Interpolator(properties);
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, properties);
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 properties;
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 (T) appenders.get(appenderName);
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         appenders.putIfAbsent(appender.getName(), appender);
670     }
671 
672     @Override
673     public StrSubstitutor getStrSubstitutor() {
674         return subst;
675     }
676 
677     @Override
678     public void setAdvertiser(final Advertiser advertiser) {
679         this.advertiser = advertiser;
680     }
681 
682     @Override
683     public Advertiser getAdvertiser() {
684         return advertiser;
685     }
686 
687     /*
688      * (non-Javadoc)
689      *
690      * @see org.apache.logging.log4j.core.config.ReliabilityStrategyFactory#getReliabilityStrategy(org.apache.logging.log4j
691      * .core.config.LoggerConfig)
692      */
693     @Override
694     public ReliabilityStrategy getReliabilityStrategy(final LoggerConfig loggerConfig) {
695         return ReliabilityStrategyFactory.getReliabilityStrategy(loggerConfig);
696     }
697 
698     /**
699      * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the same name is
700      * being updated at the same time.
701      *
702      * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
703      *
704      * @param logger The Logger the Appender will be associated with.
705      * @param appender The Appender.
706      */
707     @Override
708     public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
709             final Appender appender) {
710         final String loggerName = logger.getName();
711         appenders.putIfAbsent(appender.getName(), appender);
712         final LoggerConfig lc = getLoggerConfig(loggerName);
713         if (lc.getName().equals(loggerName)) {
714             lc.addAppender(appender, null, null);
715         } else {
716             final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
717             nlc.addAppender(appender, null, null);
718             nlc.setParent(lc);
719             loggerConfigs.putIfAbsent(loggerName, nlc);
720             setParents();
721             logger.getContext().updateLoggers();
722         }
723     }
724 
725     /**
726      * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the same name is being
727      * updated at the same time.
728      *
729      * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
730      *
731      * @param logger The Logger the Footer will be associated with.
732      * @param filter The Filter.
733      */
734     @Override
735     public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) {
736         final String loggerName = logger.getName();
737         final LoggerConfig lc = getLoggerConfig(loggerName);
738         if (lc.getName().equals(loggerName)) {
739             lc.addFilter(filter);
740         } else {
741             final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), lc.isAdditive());
742             nlc.addFilter(filter);
743             nlc.setParent(lc);
744             loggerConfigs.putIfAbsent(loggerName, nlc);
745             setParents();
746             logger.getContext().updateLoggers();
747         }
748     }
749 
750     /**
751      * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the same name is being
752      * updated at the same time.
753      *
754      * Note: This method is not used when configuring via configuration. It is primarily used by unit tests.
755      *
756      * @param logger The Logger the Appender will be associated with.
757      * @param additive True if the LoggerConfig should be additive, false otherwise.
758      */
759     @Override
760     public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger, final boolean additive) {
761         final String loggerName = logger.getName();
762         final LoggerConfig lc = getLoggerConfig(loggerName);
763         if (lc.getName().equals(loggerName)) {
764             lc.setAdditive(additive);
765         } else {
766             final LoggerConfig nlc = new LoggerConfig(loggerName, lc.getLevel(), additive);
767             nlc.setParent(lc);
768             loggerConfigs.putIfAbsent(loggerName, nlc);
769             setParents();
770             logger.getContext().updateLoggers();
771         }
772     }
773 
774     /**
775      * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes the Appender
776      * from this appender list and then stops the appender. This method is synchronized in case an Appender with the
777      * same name is being added during the removal.
778      *
779      * @param appenderName the name of the appender to remove.
780      */
781     public synchronized void removeAppender(final String appenderName) {
782         for (final LoggerConfig logger : loggerConfigs.values()) {
783             logger.removeAppender(appenderName);
784         }
785         final Appender app = appenders.remove(appenderName);
786 
787         if (app != null) {
788             app.stop();
789         }
790     }
791 
792     /*
793      * (non-Javadoc)
794      *
795      * @see org.apache.logging.log4j.core.config.Configuration#getCustomLevels()
796      */
797     @Override
798     public List<CustomLevelConfig> getCustomLevels() {
799         return Collections.unmodifiableList(customLevels);
800     }
801 
802     /**
803      * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the package name as
804      * necessary or return the root LoggerConfig if no other matches were found.
805      *
806      * @param loggerName The Logger name.
807      * @return The located LoggerConfig.
808      */
809     @Override
810     public LoggerConfig getLoggerConfig(final String loggerName) {
811         LoggerConfig loggerConfig = loggerConfigs.get(loggerName);
812         if (loggerConfig != null) {
813             return loggerConfig;
814         }
815         String substr = loggerName;
816         while ((substr = NameUtil.getSubName(substr)) != null) {
817             loggerConfig = loggerConfigs.get(substr);
818             if (loggerConfig != null) {
819                 return loggerConfig;
820             }
821         }
822         return root;
823     }
824 
825     @Override
826     public LoggerContext getLoggerContext() {
827         return loggerContext.get();
828     }
829 
830     /**
831      * Returns the root Logger.
832      *
833      * @return the root Logger.
834      */
835     @Override
836     public LoggerConfig getRootLogger() {
837         return root;
838     }
839 
840     /**
841      * Returns a Map of all the LoggerConfigs.
842      *
843      * @return a Map with each entry containing the name of the Logger and the LoggerConfig.
844      */
845     @Override
846     public Map<String, LoggerConfig> getLoggers() {
847         return Collections.unmodifiableMap(loggerConfigs);
848     }
849 
850     /**
851      * Returns the LoggerConfig with the specified name.
852      *
853      * @param loggerName The Logger name.
854      * @return The LoggerConfig or null if no match was found.
855      */
856     public LoggerConfig getLogger(final String loggerName) {
857         return loggerConfigs.get(loggerName);
858     }
859 
860     /**
861      * Add a loggerConfig. The LoggerConfig must already be configured with Appenders, Filters, etc. After addLogger is
862      * called LoggerContext.updateLoggers must be called.
863      *
864      * @param loggerName The name of the Logger.
865      * @param loggerConfig The LoggerConfig.
866      */
867     @Override
868     public synchronized void addLogger(final String loggerName, final LoggerConfig loggerConfig) {
869         loggerConfigs.putIfAbsent(loggerName, loggerConfig);
870         setParents();
871     }
872 
873     /**
874      * Remove a LoggerConfig.
875      *
876      * @param loggerName The name of the Logger.
877      */
878     @Override
879     public synchronized void removeLogger(final String loggerName) {
880         loggerConfigs.remove(loggerName);
881         setParents();
882     }
883 
884     @Override
885     public void createConfiguration(final Node node, final LogEvent event) {
886         final PluginType<?> type = node.getType();
887         if (type != null && type.isDeferChildren()) {
888             node.setObject(createPluginObject(type, node, event));
889         } else {
890             for (final Node child : node.getChildren()) {
891                 createConfiguration(child, event);
892             }
893 
894             if (type == null) {
895                 if (node.getParent() != null) {
896                     LOGGER.error("Unable to locate plugin for {}", node.getName());
897                 }
898             } else {
899                 node.setObject(createPluginObject(type, node, event));
900             }
901         }
902     }
903 
904     /**
905      * Invokes a static factory method to either create the desired object or to create a builder object that creates
906      * the desired object. In the case of a factory method, it should be annotated with
907      * {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}, and each parameter should be annotated with
908      * an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with
909      * {@link org.apache.logging.log4j.core.config.plugins.PluginAttribute} must be a type that can be converted from a
910      * string using one of the {@link org.apache.logging.log4j.core.config.plugins.convert.TypeConverter TypeConverters}
911      * . Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or
912      * an array of a plugin class. Collections and Maps are currently not supported, although the factory method that is
913      * called can create these from an array.
914      *
915      * Plugins can also be created using a builder class that implements
916      * {@link org.apache.logging.log4j.core.util.Builder}. In that case, a static method annotated with
917      * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} should create the builder class, and
918      * the various fields in the builder class should be annotated similarly to the method parameters. However, instead
919      * of using PluginAttribute, one should use
920      * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be
921      * specified as the default field value instead of as an additional annotation parameter.
922      *
923      * In either case, there are also annotations for specifying a
924      * {@link org.apache.logging.log4j.core.config.Configuration} (
925      * {@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a
926      * {@link org.apache.logging.log4j.core.config.Node} (
927      * {@link org.apache.logging.log4j.core.config.plugins.PluginNode}).
928      *
929      * Although the happy path works, more work still needs to be done to log incorrect parameters. These will generally
930      * result in unhelpful InvocationTargetExceptions.
931      *
932      * @param type the type of plugin to create.
933      * @param node the corresponding configuration node for this plugin to create.
934      * @param event the LogEvent that spurred the creation of this plugin
935      * @return the created plugin object or {@code null} if there was an error setting it up.
936      * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
937      * @see org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor
938      * @see org.apache.logging.log4j.core.config.plugins.convert.TypeConverter
939      */
940     private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) {
941         final Class<?> clazz = type.getPluginClass();
942 
943         if (Map.class.isAssignableFrom(clazz)) {
944             try {
945                 return createPluginMap(node);
946             } catch (final Exception e) {
947                 LOGGER.warn("Unable to create Map for {} of class {}", type.getElementName(), clazz, e);
948             }
949         }
950 
951         if (Collection.class.isAssignableFrom(clazz)) {
952             try {
953                 return createPluginCollection(node);
954             } catch (final Exception e) {
955                 LOGGER.warn("Unable to create List for {} of class {}", type.getElementName(), clazz, e);
956             }
957         }
958 
959         return new PluginBuilder(type).withConfiguration(this).withConfigurationNode(node).forLogEvent(event).build();
960     }
961 
962     private static Map<String, ?> createPluginMap(final Node node) {
963         final Map<String, Object> map = new LinkedHashMap<>();
964         for (final Node child : node.getChildren()) {
965             final Object object = child.getObject();
966             map.put(child.getName(), object);
967         }
968         return map;
969     }
970 
971     private static Collection<?> createPluginCollection(final Node node) {
972         final List<Node> children = node.getChildren();
973         final Collection<Object> list = new ArrayList<>(children.size());
974         for (final Node child : children) {
975             final Object object = child.getObject();
976             list.add(object);
977         }
978         return list;
979     }
980 
981     private void setParents() {
982         for (final Map.Entry<String, LoggerConfig> entry : loggerConfigs.entrySet()) {
983             final LoggerConfig logger = entry.getValue();
984             String key = entry.getKey();
985             if (!key.isEmpty()) {
986                 final int i = key.lastIndexOf('.');
987                 if (i > 0) {
988                     key = key.substring(0, i);
989                     LoggerConfig parent = getLoggerConfig(key);
990                     if (parent == null) {
991                         parent = root;
992                     }
993                     logger.setParent(parent);
994                 } else {
995                     logger.setParent(root);
996                 }
997             }
998         }
999     }
1000 
1001     /**
1002      * Reads an InputStream using buffered reads into a byte array buffer. The given InputStream will remain open after
1003      * invocation of this method.
1004      *
1005      * @param is the InputStream to read into a byte array buffer.
1006      * @return a byte array of the InputStream contents.
1007      * @throws IOException if the {@code read} method of the provided InputStream throws this exception.
1008      */
1009     protected static byte[] toByteArray(final InputStream is) throws IOException {
1010         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
1011 
1012         int nRead;
1013         final byte[] data = new byte[BUF_SIZE];
1014 
1015         while ((nRead = is.read(data, 0, data.length)) != -1) {
1016             buffer.write(data, 0, nRead);
1017         }
1018 
1019         return buffer.toByteArray();
1020     }
1021 
1022     @Override
1023     public NanoClock getNanoClock() {
1024         return nanoClock;
1025     }
1026 
1027     @Override
1028     public void setNanoClock(final NanoClock nanoClock) {
1029         this.nanoClock = Objects.requireNonNull(nanoClock, "nanoClock");
1030     }
1031 }