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