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