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