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