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