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