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