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