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