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