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 */
017package org.apache.logging.log4j.core.config;
018
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.HashSet;
027import java.util.LinkedHashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Objects;
031import java.util.Set;
032import java.util.concurrent.ConcurrentHashMap;
033import java.util.concurrent.ConcurrentMap;
034import java.util.concurrent.CopyOnWriteArrayList;
035
036import org.apache.logging.log4j.Level;
037import org.apache.logging.log4j.LogManager;
038import org.apache.logging.log4j.core.Appender;
039import org.apache.logging.log4j.core.Filter;
040import org.apache.logging.log4j.core.Layout;
041import org.apache.logging.log4j.core.LogEvent;
042import org.apache.logging.log4j.core.appender.AsyncAppender;
043import org.apache.logging.log4j.core.appender.ConsoleAppender;
044import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
045import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
046import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
047import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
048import org.apache.logging.log4j.core.config.plugins.util.PluginType;
049import org.apache.logging.log4j.core.filter.AbstractFilterable;
050import org.apache.logging.log4j.core.impl.Log4jContextFactory;
051import org.apache.logging.log4j.core.layout.PatternLayout;
052import org.apache.logging.log4j.core.lookup.Interpolator;
053import org.apache.logging.log4j.core.lookup.MapLookup;
054import org.apache.logging.log4j.core.lookup.StrLookup;
055import org.apache.logging.log4j.core.lookup.StrSubstitutor;
056import org.apache.logging.log4j.core.net.Advertiser;
057import org.apache.logging.log4j.core.selector.ContextSelector;
058import org.apache.logging.log4j.core.util.Constants;
059import org.apache.logging.log4j.core.util.Loader;
060import org.apache.logging.log4j.core.util.NameUtil;
061import org.apache.logging.log4j.spi.LoggerContextFactory;
062import org.apache.logging.log4j.util.PropertiesUtil;
063
064/**
065 * The base Configuration. Many configuration implementations will extend this class.
066 */
067public 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<>();
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<>();
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}