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.ConfigurationStrSubstitutor;
58  import org.apache.logging.log4j.core.lookup.Interpolator;
59  import org.apache.logging.log4j.core.lookup.MapLookup;
60  import org.apache.logging.log4j.core.lookup.RuntimeStrSubstitutor;
61  import org.apache.logging.log4j.core.lookup.StrLookup;
62  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
63  import org.apache.logging.log4j.core.net.Advertiser;
64  import org.apache.logging.log4j.core.script.AbstractScript;
65  import org.apache.logging.log4j.core.script.ScriptManager;
66  import org.apache.logging.log4j.core.script.ScriptRef;
67  import org.apache.logging.log4j.core.util.Constants;
68  import org.apache.logging.log4j.core.util.DummyNanoClock;
69  import org.apache.logging.log4j.core.util.Loader;
70  import org.apache.logging.log4j.core.util.NameUtil;
71  import org.apache.logging.log4j.core.util.NanoClock;
72  import org.apache.logging.log4j.core.util.Source;
73  import org.apache.logging.log4j.core.util.WatchManager;
74  import org.apache.logging.log4j.core.util.Watcher;
75  import org.apache.logging.log4j.core.util.WatcherFactory;
76  import org.apache.logging.log4j.util.PropertiesUtil;
77  
78  /**
79   * The base Configuration. Many configuration implementations will extend this class.
80   */
81  public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration {
82  
83      private static final int BUF_SIZE = 16384;
84  
85      /**
86       * The root node of the configuration.
87       */
88      protected Node rootNode;
89  
90      /**
91       * Listeners for configuration changes.
92       */
93      protected final List<ConfigurationListener> listeners = new CopyOnWriteArrayList<>();
94  
95      /**
96       * Packages found in configuration "packages" attribute.
97       */
98      protected final List<String> pluginPackages = new ArrayList<>();
99  
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 }