View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.config;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.Serializable;
23  import java.lang.ref.WeakReference;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.HashSet;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Objects;
33  import java.util.Set;
34  import java.util.concurrent.ConcurrentHashMap;
35  import java.util.concurrent.ConcurrentMap;
36  import java.util.concurrent.CopyOnWriteArrayList;
37  import java.util.concurrent.TimeUnit;
38  
39  import org.apache.logging.log4j.Level;
40  import org.apache.logging.log4j.core.Appender;
41  import org.apache.logging.log4j.core.Filter;
42  import org.apache.logging.log4j.core.Layout;
43  import org.apache.logging.log4j.core.LifeCycle2;
44  import org.apache.logging.log4j.core.LogEvent;
45  import org.apache.logging.log4j.core.LoggerContext;
46  import org.apache.logging.log4j.core.Version;
47  import org.apache.logging.log4j.core.appender.AsyncAppender;
48  import org.apache.logging.log4j.core.appender.ConsoleAppender;
49  import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
50  import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate;
51  import org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor;
52  import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
53  import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
54  import org.apache.logging.log4j.core.config.plugins.util.PluginType;
55  import org.apache.logging.log4j.core.filter.AbstractFilterable;
56  import org.apache.logging.log4j.core.layout.PatternLayout;
57  import org.apache.logging.log4j.core.lookup.Interpolator;
58  import org.apache.logging.log4j.core.lookup.MapLookup;
59  import org.apache.logging.log4j.core.lookup.StrLookup;
60  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
61  import org.apache.logging.log4j.core.net.Advertiser;
62  import org.apache.logging.log4j.core.script.AbstractScript;
63  import org.apache.logging.log4j.core.script.ScriptManager;
64  import org.apache.logging.log4j.core.script.ScriptRef;
65  import org.apache.logging.log4j.core.util.Constants;
66  import org.apache.logging.log4j.core.util.DummyNanoClock;
67  import org.apache.logging.log4j.core.util.Loader;
68  import org.apache.logging.log4j.core.util.NameUtil;
69  import org.apache.logging.log4j.core.util.NanoClock;
70  import org.apache.logging.log4j.core.util.Source;
71  import org.apache.logging.log4j.core.util.WatchManager;
72  import org.apache.logging.log4j.core.util.Watcher;
73  import org.apache.logging.log4j.core.util.WatcherFactory;
74  import org.apache.logging.log4j.util.PropertiesUtil;
75  
76  /**
77   * The base Configuration. Many configuration implementations will extend this class.
78   */
79  public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration {
80  
81      private static final int BUF_SIZE = 16384;
82  
83      /**
84       * The root node of the configuration.
85       */
86      protected Node rootNode;
87  
88      /**
89       * Listeners for configuration changes.
90       */
91      protected final List<ConfigurationListener> listeners = new CopyOnWriteArrayList<>();
92  
93      /**
94       * Packages found in configuration "packages" attribute.
95       */
96      protected final List<String> pluginPackages = new ArrayList<>();
97  
98      /**
99       * 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 }