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