    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements. See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache license, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License. You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the license for the specific language governing permissions and
    015     * limitations under the license.
    016     */
    017    package org.apache.logging.log4j.core.config;
    018
    019    import java.io.ByteArrayOutputStream;
    020    import java.io.IOException;
    021    import java.io.InputStream;
    022    import java.io.Serializable;
    023    import java.util.ArrayList;
    024    import java.util.Collection;
    025    import java.util.Collections;
    026    import java.util.HashSet;
    027    import java.util.LinkedHashMap;
    028    import java.util.List;
    029    import java.util.Map;
    030    import java.util.Set;
    031    import java.util.concurrent.ConcurrentHashMap;
    032    import java.util.concurrent.ConcurrentMap;
    033    import java.util.concurrent.CopyOnWriteArrayList;
    034
    035    import org.apache.logging.log4j.Level;
    036    import org.apache.logging.log4j.LogManager;
    037    import org.apache.logging.log4j.core.Appender;
    038    import org.apache.logging.log4j.core.Filter;
    039    import org.apache.logging.log4j.core.Layout;
    040    import org.apache.logging.log4j.core.LogEvent;
    041    import org.apache.logging.log4j.core.appender.AsyncAppender;
    042    import org.apache.logging.log4j.core.appender.ConsoleAppender;
    043    import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
    044    import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
    045    import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
    046    import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
    047    import org.apache.logging.log4j.core.config.plugins.util.PluginType;
    048    import org.apache.logging.log4j.core.filter.AbstractFilterable;
    049    import org.apache.logging.log4j.core.impl.Log4jContextFactory;
    050    import org.apache.logging.log4j.core.layout.PatternLayout;
    051    import org.apache.logging.log4j.core.lookup.Interpolator;
    052    import org.apache.logging.log4j.core.lookup.MapLookup;
    053    import org.apache.logging.log4j.core.lookup.StrLookup;
    054    import org.apache.logging.log4j.core.lookup.StrSubstitutor;
    055    import org.apache.logging.log4j.core.net.Advertiser;
    056    import org.apache.logging.log4j.core.selector.ContextSelector;
    057    import org.apache.logging.log4j.core.util.Assert;
    058    import org.apache.logging.log4j.core.util.Constants;
    059    import org.apache.logging.log4j.core.util.Loader;
    060    import org.apache.logging.log4j.core.util.NameUtil;
    061    import org.apache.logging.log4j.spi.LoggerContextFactory;
    062    import org.apache.logging.log4j.util.PropertiesUtil;
    063
    064    /**
    065     * The base Configuration. Many configuration implementations will extend this class.
    066     */
    067    public abstract class AbstractConfiguration extends AbstractFilterable implements Configuration {
    068
    069        private static final long serialVersionUID = 1L;
    070
    071        private static final int BUF_SIZE = 16384;
    072
    073        /**
    074         * The root node of the configuration.
    075         */
    076        protected Node rootNode;
    077
    078        /**
    079         * Listeners for configuration changes.
    080         */
    081        protected final List<ConfigurationListener> listeners = new CopyOnWriteArrayList<ConfigurationListener>();
    082
    083        /**
    084         * The ConfigurationMonitor that checks for configuration changes.
    085         */
    086        protected ConfigurationMonitor monitor = new DefaultConfigurationMonitor();
    087
    088        /**
    089         * The Advertiser which exposes appender configurations to external systems.
    090         */
    091        private Advertiser advertiser = new DefaultAdvertiser();
    092        private Node advertiserNode = null;
    093        private Object advertisement;
    094
    095        /**
    096         *
    097         */
    098        protected boolean isShutdownHookEnabled = true;
    099        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    }