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