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.util.ArrayList;
    24  import java.util.Collection;
    25  import java.util.Collections;
    26  import java.util.HashSet;
    27  import java.util.LinkedHashMap;
    28  import java.util.List;
    29  import java.util.Map;
    30  import java.util.Set;
    31  import java.util.concurrent.ConcurrentHashMap;
    32  import java.util.concurrent.ConcurrentMap;
    33  import java.util.concurrent.CopyOnWriteArrayList;
    34
    35  import org.apache.logging.log4j.Level;
    36  import org.apache.logging.log4j.LogManager;
    37  import org.apache.logging.log4j.core.Appender;
    38  import org.apache.logging.log4j.core.Filter;
    39  import org.apache.logging.log4j.core.Layout;
    40  import org.apache.logging.log4j.core.LogEvent;
    41  import org.apache.logging.log4j.core.appender.AsyncAppender;
    42  import org.apache.logging.log4j.core.appender.ConsoleAppender;
    43  import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
    44  import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
    45  import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
    46  import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
    47  import org.apache.logging.log4j.core.config.plugins.util.PluginType;
    48  import org.apache.logging.log4j.core.filter.AbstractFilterable;
    49  import org.apache.logging.log4j.core.impl.Log4jContextFactory;
    50  import org.apache.logging.log4j.core.layout.PatternLayout;
    51  import org.apache.logging.log4j.core.lookup.Interpolator;
    52  import org.apache.logging.log4j.core.lookup.MapLookup;
    53  import org.apache.logging.log4j.core.lookup.StrLookup;
    54  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
    55  import org.apache.logging.log4j.core.net.Advertiser;
    56  import org.apache.logging.log4j.core.selector.ContextSelector;
    57  import org.apache.logging.log4j.core.util.Assert;
    58  import org.apache.logging.log4j.core.util.Constants;
    59  import org.apache.logging.log4j.core.util.Loader;
    60  import org.apache.logging.log4j.core.util.NameUtil;
    61  import org.apache.logging.log4j.spi.LoggerContextFactory;
    62  import org.apache.logging.log4j.util.PropertiesUtil;
    63
    64  /**
    65   * The base Configuration. Many configuration implementations will extend this class.
    66   */
    67  public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration {
    68
    69      private static final long serialVersionUID = 1L;
    70
    71      private static final int BUF_SIZE = 16384;
    72
    73      /**
    74       * The root node of the configuration.
    75       */
    76      protected Node rootNode;
    77
    78      /**
    79       * Listeners for configuration changes.
    80       */
    81      protected final List<ConfigurationListener> listeners = new CopyOnWriteArrayList<ConfigurationListener>();
    82
    83      /**
    84       * The ConfigurationMonitor that checks for configuration changes.
    85       */
    86      protected ConfigurationMonitor monitor = new DefaultConfigurationMonitor();
    87
    88      /**
    89       * The Advertiser which exposes appender configurations to external systems.
    90       */
    91      private Advertiser advertiser = new DefaultAdvertiser();
    92      private Node advertiserNode = null;
    93      private Object advertisement;
    94
    95      /**
    96       *
    97       */
    98      protected boolean isShutdownHookEnabled = true;
    99      private String name;
    100     private ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<String, Appender>();
    101     private ConcurrentMap<String, LoggerConfig> loggers = new ConcurrentHashMap<String, LoggerConfig>();
    102     private List<CustomLevelConfig> customLevels = Collections.emptyList();
    103     private final ConcurrentMap<String, String> properties = new ConcurrentHashMap<String, String>();
    104     private final StrLookup tempLookup = new Interpolator(properties);
    105     private final StrSubstitutor subst = new StrSubstitutor(tempLookup);
    106     private LoggerConfig root = new LoggerConfig();
    107     private final ConcurrentMap<String, Object> componentMap = new ConcurrentHashMap<String, Object>();
    108     protected final List<String> pluginPackages = new ArrayList<String>();
    109     protected PluginManager pluginManager;
    110     private final ConfigurationSource configurationSource;
    111
    112     /**
    113      * Constructor.
    114      */
    115     protected AbstractConfiguration(final ConfigurationSource configurationSource) {
    116         this.configurationSource = Assert.requireNonNull(configurationSource, "configurationSource is null");
    117         componentMap.put(Configuration.CONTEXT_PROPERTIES, properties);
    118         pluginManager = new PluginManager(Node.CATEGORY);
    119         rootNode = new Node();
    120     }
    121
    122     @Override
    123     public ConfigurationSource getConfigurationSource() {
    124         return configurationSource;
    125     }
    126
    127     @Override
    128     public List<String> getPluginPackages() {
    129         return pluginPackages;
    130     }
    131
    132     @Override
    133     public Map<String, String> getProperties() {
    134         return properties;
    135     }
    136
    137     /**
    138      * Initialize the configuration.
    139      */
    140     @Override
    141     public void start() {
    142         LOGGER.debug("Starting configuration {}", this);
    143         this.setStarting();
    144         pluginManager.collectPlugins(pluginPackages);
    145         final PluginManager levelPlugins = new PluginManager(Level.CATEGORY);
    146         levelPlugins.collectPlugins(pluginPackages);
    147         final Map<String, PluginType<?>> plugins = levelPlugins.getPlugins();
    148         if (plugins != null) {
    149             for (final PluginType<?> type : plugins.values()) {
    150                 try {
    151                     // Cause the class to be initialized if it isn't already.
    152                     Loader.initializeClass(type.getPluginClass().getName(), type.getPluginClass().getClassLoader());
    153                 } catch (final Exception e) {
    154                     LOGGER.error("Unable to initialize {} due to {}", type.getPluginClass().getName(), e.getClass()
    155                             .getSimpleName(), e);
    156                 }
    157             }
    158         }
    159         setup();
    160         setupAdvertisement();
    161         doConfigure();
    162         final Set<LoggerConfig> alreadyStarted = new HashSet<LoggerConfig>();
    163         for (final LoggerConfig logger : loggers.values()) {
    164             logger.start();
    165             alreadyStarted.add(logger);
    166         }
    167         for (final Appender appender : appenders.values()) {
    168             appender.start();
    169         }
    170         if (!alreadyStarted.contains(root)) { // LOG4J2-392
    171             root.start(); // LOG4J2-336
    172         }
    173         super.start();
    174         LOGGER.debug("Started configuration {} OK.", this);
    175     }
    176
    177     /**
    178      * Tear down the configuration.
    179      */
    180     @Override
    181     public void stop() {
    182         this.setStopping();
    183         LOGGER.trace("Stopping {}...", this);
    184
    185         // LOG4J2-392 first stop AsyncLogger Disruptor thread
    186         final LoggerContextFactory factory = LogManager.getFactory();
    187         if (factory instanceof Log4jContextFactory) {
    188             final ContextSelector selector = ((Log4jContextFactory) factory).getSelector();
    189             if (selector instanceof AsyncLoggerContextSelector) { // all loggers are async
    190                 // TODO until LOG4J2-493 is fixed we can only stop AsyncLogger once!
    191                 // but LoggerContext.setConfiguration will call config.stop()
    192                 // every time the configuration changes...
    193                 //
    194                 // Uncomment the line below after LOG4J2-493 is fixed
    195                 //AsyncLogger.stop();
    196                 //LOGGER.trace("AbstractConfiguration stopped AsyncLogger disruptor.");
    197             }
    198         }
    199         // similarly, first stop AsyncLoggerConfig Disruptor thread(s)
    200         final Set<LoggerConfig> alreadyStopped = new HashSet<LoggerConfig>();
    201         int asyncLoggerConfigCount = 0;
    202         for (final LoggerConfig logger : loggers.values()) {
    203             if (logger instanceof AsyncLoggerConfig) {
    204                 // LOG4J2-520, LOG4J2-392:
    205                 // Important: do not clear appenders until after all AsyncLoggerConfigs
    206                 // have been stopped! Stopping the last AsyncLoggerConfig will
    207                 // shut down the disruptor and wait for all enqueued events to be processed.
    208                 // Only *after this* the appenders can be cleared or events will be lost.
    209                 logger.stop();
    210                 asyncLoggerConfigCount++;
    211                 alreadyStopped.add(logger);
    212             }
    213         }
    214         if (root instanceof AsyncLoggerConfig & !alreadyStopped.contains(root)) { // LOG4J2-807
    215             root.stop();
    216             asyncLoggerConfigCount++;
    217             alreadyStopped.add(root);
    218         }
    219         LOGGER.trace("AbstractConfiguration stopped {} AsyncLoggerConfigs.", asyncLoggerConfigCount);
    220
    221         // Stop the appenders in reverse order in case they still have activity.
    222         final Appender[] array = appenders.values().toArray(new Appender[appenders.size()]);
    223
    224         // LOG4J2-511, LOG4J2-392 stop AsyncAppenders first
    225         int asyncAppenderCount = 0;
    226         for (int i = array.length - 1; i >= 0; --i) {
    227             if (array[i] instanceof AsyncAppender) {
    228                 array[i].stop();
    229                 asyncAppenderCount++;
    230             }
    231         }
    232         LOGGER.trace("AbstractConfiguration stopped {} AsyncAppenders.", asyncAppenderCount);
    233
    234         int appenderCount = 0;
    235         for (int i = array.length - 1; i >= 0; --i) {
    236             if (array[i].isStarted()) { // then stop remaining Appenders
    237                 array[i].stop();
    238                 appenderCount++;
    239             }
    240         }
    241         LOGGER.trace("AbstractConfiguration stopped {} Appenders.", appenderCount);
    242
    243         int loggerCount = 0;
    244         for (final LoggerConfig logger : loggers.values()) {
    245             // clear appenders, even if this logger is already stopped.
    246             logger.clearAppenders();
    247
    248             // AsyncLoggerConfigHelper decreases its ref count when an AsyncLoggerConfig is stopped.
    249             // Stopping the same AsyncLoggerConfig twice results in an incorrect ref count and
    250             // the shared Disruptor may be shut down prematurely, resulting in NPE or other errors.
    251             if (alreadyStopped.contains(logger)) {
    252                 continue;
    253             }
    254             logger.stop();
    255             loggerCount++;
    256         }
    257         LOGGER.trace("AbstractConfiguration stopped {} Loggers.", loggerCount);
    258
    259         // AsyncLoggerConfigHelper decreases its ref count when an AsyncLoggerConfig is stopped.
    260         // Stopping the same AsyncLoggerConfig twice results in an incorrect ref count and
    261         // the shared Disruptor may be shut down prematurely, resulting in NPE or other errors.
    262         if (!alreadyStopped.contains(root)) {
    263             root.stop();
    264         }
    265         super.stop();
    266         if (advertiser != null && advertisement != null) {
    267             advertiser.unadvertise(advertisement);
    268         }
    269         LOGGER.debug("Stopped {} OK", this);
    270     }
    271
    272     @Override
    273     public boolean isShutdownHookEnabled() {
    274         return isShutdownHookEnabled;
    275     }
    276
    277     protected void setup() {
    278     }
    279
    280     protected Level getDefaultStatus() {
    281         final String statusLevel = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_DEFAULT_STATUS_LEVEL,
    282             Level.ERROR.name());
    283         try {
    284             return Level.toLevel(statusLevel);
    285         } catch (final Exception ex) {
    286             return Level.ERROR;
    287         }
    288     }
    289
    290     protected void createAdvertiser(final String advertiserString, final ConfigurationSource configSource,
    291                                     final byte[] buffer, final String contentType) {
    292         if (advertiserString != null) {
    293             final Node node = new Node(null, advertiserString, null);
    294             final Map<String, String> attributes = node.getAttributes();
    295             attributes.put("content", new String(buffer));
    296             attributes.put("contentType", contentType);
    297             attributes.put("name", "configuration");
    298             if (configSource.getLocation() != null) {
    299                 attributes.put("location", configSource.getLocation());
    300             }
    301             advertiserNode = node;
    302         }
    303     }
    304
    305     private void setupAdvertisement() {
    306         if (advertiserNode != null)
    307         {
    308             final String name = advertiserNode.getName();
    309             final PluginType<?> type = pluginManager.getPluginType(name);
    310             if (type != null)
    311             {
    312                 final Class<? extends Advertiser> clazz = type.getPluginClass().asSubclass(Advertiser.class);
    313                 try {
    314                     advertiser = clazz.newInstance();
    315                     advertisement = advertiser.advertise(advertiserNode.getAttributes());
    316                 } catch (final InstantiationException e) {
    317                     LOGGER.error("InstantiationException attempting to instantiate advertiser: {}", name, e);
    318                 } catch (final IllegalAccessException e) {
    319                     LOGGER.error("IllegalAccessException attempting to instantiate advertiser: {}", name, e);
    320                 }
    321             }
    322         }
    323     }
    324
    325     @SuppressWarnings("unchecked")
    326     @Override
    327     public <T> T getComponent(final String name) {
    328         return (T) componentMap.get(name);
    329     }
    330
    331     @Override
    332     public void addComponent(final String name, final Object obj) {
    333         componentMap.putIfAbsent(name, obj);
    334     }
    335
    336     protected void doConfigure() {
    337         if (rootNode.hasChildren() && rootNode.getChildren().get(0).getName().equalsIgnoreCase("Properties")) {
    338             final Node first = rootNode.getChildren().get(0);
    339             createConfiguration(first, null);
    340             if (first.getObject() != null) {
    341                 subst.setVariableResolver((StrLookup) first.getObject());
    342             }
    343         } else {
    344             final Map<String, String> map = this.getComponent(CONTEXT_PROPERTIES);
    345             final StrLookup lookup = map == null ? null : new MapLookup(map);
    346             subst.setVariableResolver(new Interpolator(lookup, pluginPackages));
    347         }
    348
    349         boolean setLoggers = false;
    350         boolean setRoot = false;
    351         for (final Node child : rootNode.getChildren()) {
    352             if (child.getName().equalsIgnoreCase("Properties")) {
    353                 if (tempLookup == subst.getVariableResolver()) {
    354                     LOGGER.error("Properties declaration must be the first element in the configuration");
    355                 }
    356                 continue;
    357             }
    358             createConfiguration(child, null);
    359             if (child.getObject() == null) {
    360                 continue;
    361             }
    362             if (child.getName().equalsIgnoreCase("Appenders")) {
    363                 appenders = child.getObject();
    364             } else if (child.isInstanceOf(Filter.class)) {
    365                 addFilter(child.getObject(Filter.class));
    366             } else if (child.getName().equalsIgnoreCase("Loggers")) {
    367                 final Loggers l = child.getObject();
    368                 loggers = l.getMap();
    369                 setLoggers = true;
    370                 if (l.getRoot() != null) {
    371                     root = l.getRoot();
    372                     setRoot = true;
    373                 }
    374             } else if (child.getName().equalsIgnoreCase("CustomLevels")) {
    375                 customLevels = child.getObject(CustomLevels.class).getCustomLevels();
    376             } else if (child.isInstanceOf(CustomLevelConfig.class)) {
    377                 final List<CustomLevelConfig> copy = new ArrayList<CustomLevelConfig>(customLevels);
    378                 copy.add(child.getObject(CustomLevelConfig.class));
    379                 customLevels = copy;
    380             } else {
    381                 LOGGER.error("Unknown object \"{}\" of type {} is ignored.", child.getName(),
    382                         child.getObject().getClass().getName());
    383             }
    384         }
    385
    386         if (!setLoggers) {
    387             LOGGER.warn("No Loggers were configured, using default. Is the Loggers element missing?");
    388             setToDefault();
    389             return;
    390         } else if (!setRoot) {
    391             LOGGER.warn("No Root logger was configured, creating default ERROR-level Root logger with Console appender");
    392             setToDefault();
    393             // return; // LOG4J2-219: creating default root=ok, but don't exclude configured Loggers
    394         }
    395
    396         for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {
    397             final LoggerConfig l = entry.getValue();
    398             for (final AppenderRef ref : l.getAppenderRefs()) {
    399                 final Appender app = appenders.get(ref.getRef());
    400                 if (app != null) {
    401                     l.addAppender(app, ref.getLevel(), ref.getFilter());
    402                 } else {
    403                     LOGGER.error("Unable to locate appender {} for logger {}", ref.getRef(), l.getName());
    404                 }
    405             }
    406
    407         }
    408
    409         setParents();
    410     }
    411
    412     private void setToDefault() {
    413         // TODO: reduce duplication between this method and DefaultConfiguration constructor
    414         setName(DefaultConfiguration.DEFAULT_NAME);
    415         final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
    416             .withPattern(DefaultConfiguration.DEFAULT_PATTERN)
    417             .withConfiguration(this)
    418             .build();
    419         final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
    420         appender.start();
    421         addAppender(appender);
    422         final LoggerConfig root = getRootLogger();
    423         root.addAppender(appender, null, null);
    424
    425         final String levelName = PropertiesUtil.getProperties().getStringProperty(DefaultConfiguration.DEFAULT_LEVEL);
    426         final Level level = levelName != null && Level.getLevel(levelName) != null ?
    427             Level.getLevel(levelName) : Level.ERROR;
    428         root.setLevel(level);
    429     }
    430
    431     /**
    432      * Set the name of the configuration.
    433      * @param name The name.
    434      */
    435     public void setName(final String name) {
    436         this.name = name;
    437     }
    438
    439     /**
    440      * Returns the name of the configuration.
    441      * @return the name of the configuration.
    442      */
    443     @Override
    444     public String getName() {
    445         return name;
    446     }
    447
    448     /**
    449      * Add a listener for changes on the configuration.
    450      * @param listener The ConfigurationListener to add.
    451      */
    452     @Override
    453     public void addListener(final ConfigurationListener listener) {
    454         listeners.add(listener);
    455     }
    456
    457     /**
    458      * Remove a ConfigurationListener.
    459      * @param listener The ConfigurationListener to remove.
    460      */
    461     @Override
    462     public void removeListener(final ConfigurationListener listener) {
    463         listeners.remove(listener);
    464     }
    465
    466     /**
    467      * Returns the Appender with the specified name.
    468      * @param name The name of the Appender.
    469      * @return the Appender with the specified name or null if the Appender cannot be located.
    470      */
    471     @Override
    472     public Appender getAppender(final String name) {
    473         return appenders.get(name);
    474     }
    475
    476     /**
    477      * Returns a Map containing all the Appenders and their name.
    478      * @return A Map containing each Appender's name and the Appender object.
    479      */
    480     @Override
    481     public Map<String, Appender> getAppenders() {
    482         return appenders;
    483     }
    484
    485     /**
    486      * Adds an Appender to the configuration.
    487      * @param appender The Appender to add.
    488      */
    489     @Override
    490     public void addAppender(final Appender appender) {
    491         appenders.putIfAbsent(appender.getName(), appender);
    492     }
    493
    494     @Override
    495     public StrSubstitutor getStrSubstitutor() {
    496         return subst;
    497     }
    498
    499     @Override
    500     public void setConfigurationMonitor(final ConfigurationMonitor monitor) {
    501         this.monitor = monitor;
    502     }
    503
    504     @Override
    505     public ConfigurationMonitor getConfigurationMonitor() {
    506         return monitor;
    507     }
    508
    509     @Override
    510     public void setAdvertiser(final Advertiser advertiser) {
    511         this.advertiser = advertiser;
    512     }
    513
    514     @Override
    515     public Advertiser getAdvertiser() {
    516         return advertiser;
    517     }
    518
    519     /**
    520      * Associates an Appender with a LoggerConfig. This method is synchronized in case a Logger with the
    521      * same name is being updated at the same time.
    522      *
    523      * Note: This method is not used when configuring via configuration. It is primarily used by
    524      * unit tests.
    525      * @param logger The Logger the Appender will be associated with.
    526      * @param appender The Appender.
    527      */
    528     @Override
    529     public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
    530                                                final Appender appender) {
    531         final String name = logger.getName();
    532         appenders.putIfAbsent(appender.getName(), appender);
    533         final LoggerConfig lc = getLoggerConfig(name);
    534         if (lc.getName().equals(name)) {
    535             lc.addAppender(appender, null, null);
    536         } else {
    537             final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive());
    538             nlc.addAppender(appender, null, null);
    539             nlc.setParent(lc);
    540             loggers.putIfAbsent(name, nlc);
    541             setParents();
    542             logger.getContext().updateLoggers();
    543         }
    544     }
    545     /**
    546      * Associates a Filter with a LoggerConfig. This method is synchronized in case a Logger with the
    547      * same name is being updated at the same time.
    548      *
    549      * Note: This method is not used when configuring via configuration. It is primarily used by
    550      * unit tests.
    551      * @param logger The Logger the Fo;ter will be associated with.
    552      * @param filter The Filter.
    553      */
    554     @Override
    555     public synchronized void addLoggerFilter(final org.apache.logging.log4j.core.Logger logger, final Filter filter) {
    556         final String name = logger.getName();
    557         final LoggerConfig lc = getLoggerConfig(name);
    558         if (lc.getName().equals(name)) {
    559
    560             lc.addFilter(filter);
    561         } else {
    562             final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), lc.isAdditive());
    563             nlc.addFilter(filter);
    564             nlc.setParent(lc);
    565             loggers.putIfAbsent(name, nlc);
    566             setParents();
    567             logger.getContext().updateLoggers();
    568         }
    569     }
    570     /**
    571      * Marks a LoggerConfig as additive. This method is synchronized in case a Logger with the
    572      * same name is being updated at the same time.
    573      *
    574      * Note: This method is not used when configuring via configuration. It is primarily used by
    575      * unit tests.
    576      * @param logger The Logger the Appender will be associated with.
    577      * @param additive True if the LoggerConfig should be additive, false otherwise.
    578      */
    579     @Override
    580     public synchronized void setLoggerAdditive(final org.apache.logging.log4j.core.Logger logger,
    581                                                final boolean additive) {
    582         final String name = logger.getName();
    583         final LoggerConfig lc = getLoggerConfig(name);
    584         if (lc.getName().equals(name)) {
    585             lc.setAdditive(additive);
    586         } else {
    587             final LoggerConfig nlc = new LoggerConfig(name, lc.getLevel(), additive);
    588             nlc.setParent(lc);
    589             loggers.putIfAbsent(name, nlc);
    590             setParents();
    591             logger.getContext().updateLoggers();
    592         }
    593     }
    594
    595     /**
    596      * Remove an Appender. First removes any associations between LoggerConfigs and the Appender, removes
    597      * the Appender from this appender list and then stops the appender. This method is synchronized in
    598      * case an Appender with the same name is being added during the removal.
    599      * @param name the name of the appender to remove.
    600      */
    601     public synchronized void removeAppender(final String name) {
    602         for (final LoggerConfig logger : loggers.values()) {
    603             logger.removeAppender(name);
    604         }
    605         final Appender app = appenders.remove(name);
    606
    607         if (app != null) {
    608             app.stop();
    609         }
    610     }
    611
    612     /*
    613      * (non-Javadoc)
    614      * @see org.apache.logging.log4j.core.config.Configuration#getCustomLevels()
    615      */
    616     @Override
    617     public List<CustomLevelConfig> getCustomLevels() {
    618         return Collections.unmodifiableList(customLevels);
    619     }
    620
    621     /**
    622      * Locates the appropriate LoggerConfig for a Logger name. This will remove tokens from the
    623      * package name as necessary or return the root LoggerConfig if no other matches were found.
    624      * @param name The Logger name.
    625      * @return The located LoggerConfig.
    626      */
    627     @Override
    628     public LoggerConfig getLoggerConfig(final String name) {
    629         if (loggers.containsKey(name)) {
    630             return loggers.get(name);
    631         }
    632         String substr = name;
    633         while ((substr = NameUtil.getSubName(substr)) != null) {
    634             if (loggers.containsKey(substr)) {
    635                 return loggers.get(substr);
    636             }
    637         }
    638         return root;
    639     }
    640
    641     /**
    642      * Returns the root Logger.
    643      * @return the root Logger.
    644      */
    645     public LoggerConfig getRootLogger() {
    646         return root;
    647     }
    648
    649     /**
    650      * Returns a Map of all the LoggerConfigs.
    651      * @return a Map with each entry containing the name of the Logger and the LoggerConfig.
    652      */
    653     @Override
    654     public Map<String, LoggerConfig> getLoggers() {
    655         return Collections.unmodifiableMap(loggers);
    656     }
    657
    658     /**
    659      * Returns the LoggerConfig with the specified name.
    660      * @param name The Logger name.
    661      * @return The LoggerConfig or null if no match was found.
    662      */
    663     public LoggerConfig getLogger(final String name) {
    664         return loggers.get(name);
    665     }
    666
    667     /**
    668      * Add a loggerConfig. The LoggerConfig must already be configured with Appenders, Filters, etc.
    669      * After addLogger is called LoggerContext.updateLoggers must be called.
    670      *
    671      * @param name The name of the Logger.
    672      * @param loggerConfig The LoggerConfig.
    673      */
    674     @Override
    675     public synchronized void addLogger(final String name, final LoggerConfig loggerConfig) {
    676         loggers.putIfAbsent(name, loggerConfig);
    677         setParents();
    678     }
    679
    680     /**
    681      * Remove a LoggerConfig.
    682      *
    683      * @param name The name of the Logger.
    684      */
    685     @Override
    686     public synchronized void removeLogger(final String name) {
    687         loggers.remove(name);
    688         setParents();
    689     }
    690
    691     @Override
    692     public void createConfiguration(final Node node, final LogEvent event) {
    693         final PluginType<?> type = node.getType();
    694         if (type != null && type.isDeferChildren()) {
    695             node.setObject(createPluginObject(type, node, event));
    696         } else {
    697             for (final Node child : node.getChildren()) {
    698                 createConfiguration(child, event);
    699             }
    700
    701             if (type == null) {
    702                 if (node.getParent() != null) {
    703                     LOGGER.error("Unable to locate plugin for {}", node.getName());
    704                 }
    705             } else {
    706                 node.setObject(createPluginObject(type, node, event));
    707             }
    708         }
    709     }
    710
    711    /**
    712     * Invokes a static factory method to either create the desired object or to create a builder object that creates
    713     * the desired object. In the case of a factory method, it should be annotated with
    714     * {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}, and each parameter should be annotated with
    715     * an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with
    716     * {@link org.apache.logging.log4j.core.config.plugins.PluginAttribute} must be a type that can be converted from
    717     * a string using one of the {@link org.apache.logging.log4j.core.config.plugins.convert.TypeConverter TypeConverters}.
    718     * Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or an
    719     * array of a plugin class. Collections and Maps are currently not supported, although the factory method that is
    720     * called can create these from an array.
    721     *
    722     * Plugins can also be created using a builder class that implements
    723     * {@link org.apache.logging.log4j.core.util.Builder}. In that case, a static method annotated with
    724     * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} should create the builder class,
    725     * and the various fields in the builder class should be annotated similarly to the method parameters. However,
    726     * instead of using PluginAttribute, one should use
    727     * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be
    728     * specified as the default field value instead of as an additional annotation parameter.
    729     *
    730     * In either case, there are also annotations for specifying a
    731     * {@link org.apache.logging.log4j.core.config.Configuration}
    732     * ({@link org.apache.logging.log4j.core.config.plugins.PluginConfiguration}) or a
    733     * {@link org.apache.logging.log4j.core.config.Node}
    734     * ({@link org.apache.logging.log4j.core.config.plugins.PluginNode}).
    735     *
    736     * Although the happy path works, more work still needs to be done to log incorrect
    737     * parameters. These will generally result in unhelpful InvocationTargetExceptions.
    738     *
    739     * @param type the type of plugin to create.
    740     * @param node the corresponding configuration node for this plugin to create.
    741     * @param event the LogEvent that spurred the creation of this plugin
    742     * @return the created plugin object or {@code null} if there was an error setting it up.
    743     * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
    744     * @see org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor
    745     * @see org.apache.logging.log4j.core.config.plugins.convert.TypeConverter
    746     */
    747     private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) {
    748         final Class<?> clazz = type.getPluginClass();
    749
    750         if (Map.class.isAssignableFrom(clazz)) {
    751             try {
    752                 return createPluginMap(node);
    753             } catch (final Exception e) {
    754                 LOGGER.warn("Unable to create Map for {} of class {}", type.getElementName(), clazz, e);
    755             }
    756         }
    757
    758         if (Collection.class.isAssignableFrom(clazz)) {
    759             try {
    760                 return createPluginCollection(node);
    761             } catch (final Exception e) {
    762                 LOGGER.warn("Unable to create List for {} of class {}", type.getElementName(), clazz, e);
    763             }
    764         }
    765
    766         return new PluginBuilder(type)
    767                 .withConfiguration(this)
    768                 .withConfigurationNode(node)
    769                 .forLogEvent(event)
    770                 .build();
    771     }
    772
    773     private static Map<String, ?> createPluginMap(final Node node) {
    774         final Map<String, Object> map = new LinkedHashMap<String, Object>();
    775         for (final Node child : node.getChildren()) {
    776             final Object object = child.getObject();
    777             map.put(child.getName(), object);
    778         }
    779         return map;
    780     }
    781
    782     private static Collection<?> createPluginCollection(final Node node) {
    783         final List<Node> children = node.getChildren();
    784         final Collection<Object> list = new ArrayList<Object>(children.size());
    785         for (final Node child : children) {
    786             final Object object = child.getObject();
    787             list.add(object);
    788         }
    789         return list;
    790     }
    791
    792     private void setParents() {
    793          for (final Map.Entry<String, LoggerConfig> entry : loggers.entrySet()) {
    794             final LoggerConfig logger = entry.getValue();
    795             String name = entry.getKey();
    796             if (!name.isEmpty()) {
    797                 final int i = name.lastIndexOf('.');
    798                 if (i > 0) {
    799                     name = name.substring(0, i);
    800                     LoggerConfig parent = getLoggerConfig(name);
    801                     if (parent == null) {
    802                         parent = root;
    803                     }
    804                     logger.setParent(parent);
    805                 } else {
    806                     logger.setParent(root);
    807                 }
    808             }
    809         }
    810     }
    811
    812     /**
    813      * Reads an InputStream using buffered reads into a byte array buffer. The given InputStream will remain open
    814      * after invocation of this method.
    815      *
    816      * @param is the InputStream to read into a byte array buffer.
    817      * @return a byte array of the InputStream contents.
    818      * @throws IOException if the {@code read} method of the provided InputStream throws this exception.
    819      */
    820     protected static byte[] toByteArray(final InputStream is) throws IOException {
    821         final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    822
    823         int nRead;
    824         final byte[] data = new byte[BUF_SIZE];
    825
    826         while ((nRead = is.read(data, 0, data.length)) != -1) {
    827             buffer.write(data, 0, nRead);
    828         }
    829
    830         return buffer.toByteArray();
    831     }
    832
    833 }