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