    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.util.ArrayList;
    020    import java.util.Arrays;
    021    import java.util.Collection;
    022    import java.util.Collections;
    023    import java.util.HashMap;
    024    import java.util.Iterator;
    025    import java.util.List;
    026    import java.util.Map;
    027    import java.util.concurrent.ConcurrentHashMap;
    028    import java.util.concurrent.TimeUnit;
    029    import java.util.concurrent.atomic.AtomicBoolean;
    030    import java.util.concurrent.atomic.AtomicInteger;
    031    import java.util.concurrent.locks.Condition;
    032    import java.util.concurrent.locks.Lock;
    033    import java.util.concurrent.locks.ReentrantLock;
    034
    035    import org.apache.logging.log4j.Level;
    036    import org.apache.logging.log4j.LogManager;
    037    import org.apache.logging.log4j.Marker;
    038    import org.apache.logging.log4j.core.Appender;
    039    import org.apache.logging.log4j.core.Filter;
    040    import org.apache.logging.log4j.core.LogEvent;
    041    import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
    042    import org.apache.logging.log4j.core.config.plugins.Plugin;
    043    import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
    044    import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
    045    import org.apache.logging.log4j.core.config.plugins.PluginElement;
    046    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
    047    import org.apache.logging.log4j.core.filter.AbstractFilterable;
    048    import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
    049    import org.apache.logging.log4j.core.impl.LogEventFactory;
    050    import org.apache.logging.log4j.core.lookup.StrSubstitutor;
    051    import org.apache.logging.log4j.core.util.Booleans;
    052    import org.apache.logging.log4j.core.util.Constants;
    053    import org.apache.logging.log4j.core.util.Loader;
    054    import org.apache.logging.log4j.message.Message;
    055    import org.apache.logging.log4j.util.PropertiesUtil;
    056    import org.apache.logging.log4j.util.Strings;
    057
    058    /**
    059     * Logger object that is created via configuration.
    060     */
    061    @Plugin(name = "logger", category = Node.CATEGORY, printObject = true)
    062    public class LoggerConfig extends AbstractFilterable {
    063
    064        private static final long serialVersionUID = 1L;
    065
    066        private static final int MAX_RETRIES = 3;
    067        private static LogEventFactory LOG_EVENT_FACTORY = null;
    068
    069        private List<AppenderRef> appenderRefs = new ArrayList<AppenderRef>();
    070        private final Map<String, AppenderControl> appenders = new ConcurrentHashMap<String, AppenderControl>();
    071        private final String name;
    072        private LogEventFactory logEventFactory;
    073        private Level level;
    074        private boolean additive = true;
    075        private boolean includeLocation = true;
    076        private LoggerConfig parent;
    077        private final AtomicInteger counter = new AtomicInteger();
    078        private final AtomicBoolean shutdown = new AtomicBoolean(false);
    079        private final Map<Property, Boolean> properties;
    080        private final Configuration config;
    081        private final Lock shutdownLock = new ReentrantLock();
    082        private final Condition noLogEvents = shutdownLock.newCondition(); // should only be used when shutdown == true
    083
    084        static {
    085            final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY);
    086            if (factory != null) {
    087                try {
    088                    final Class<?> clazz = Loader.loadClass(factory);
    089                    if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) {
    090                        LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance();
    091                    }
    092                } catch (final Exception ex) {
    093                    LOGGER.error("Unable to create LogEventFactory {}", factory, ex);
    094                }
    095            }
    096            if (LOG_EVENT_FACTORY == null) {
    097                LOG_EVENT_FACTORY = new DefaultLogEventFactory();
    098            }
    099        }
    100
    101        /**
    102         * Default constructor.
    103         */
    104        public LoggerConfig() {
    105            this.logEventFactory = LOG_EVENT_FACTORY;
    106            this.level = Level.ERROR;
    107            this.name = Strings.EMPTY;
    108            this.properties = null;
    109            this.config = null;
    110        }
    111
    112        /**
    113         * Constructor that sets the name, level and additive values.
    114         *
    115         * @param name The Logger name.
    116         * @param level The Level.
    117         * @param additive true if the Logger is additive, false otherwise.
    118         */
    119        public LoggerConfig(final String name, final Level level,
    120                final boolean additive) {
    121            this.logEventFactory = LOG_EVENT_FACTORY;
    122            this.name = name;
    123            this.level = level;
    124            this.additive = additive;
    125            this.properties = null;
    126            this.config = null;
    127        }
    128
    129        protected LoggerConfig(final String name,
    130                final List<AppenderRef> appenders, final Filter filter,
    131                final Level level, final boolean additive,
    132                final Property[] properties, final Configuration config,
    133                final boolean includeLocation) {
    134            super(filter);
    135            this.logEventFactory = LOG_EVENT_FACTORY;
    136            this.name = name;
    137            this.appenderRefs = appenders;
    138            this.level = level;
    139            this.additive = additive;
    140            this.includeLocation = includeLocation;
    141            this.config = config;
    142            if (properties != null && properties.length > 0) {
    143                this.properties = new HashMap<Property, Boolean>(properties.length);
    144                for (final Property prop : properties) {
    145                    final boolean interpolate = prop.getValue().contains("${");
    146                    this.properties.put(prop, interpolate);
    147                }
    148            } else {
    149                this.properties = null;
    150            }
    151        }
    152
    153        @Override
    154        public Filter getFilter() {
    155            return super.getFilter();
    156        }
    157
    158        /**
    159         * Returns the name of the LoggerConfig.
    160         *
    161         * @return the name of the LoggerConfig.
    162         */
    163        public String getName() {
    164            return name;
    165        }
    166
    167        /**
    168         * Sets the parent of this LoggerConfig.
    169         *
    170         * @param parent the parent LoggerConfig.
    171         */
    172        public void setParent(final LoggerConfig parent) {
    173            this.parent = parent;
    174        }
    175
    176        /**
    177         * Returns the parent of this LoggerConfig.
    178         *
    179         * @return the LoggerConfig that is the parent of this one.
    180         */
    181        public LoggerConfig getParent() {
    182            return this.parent;
    183        }
    184
    185        /**
    186         * Adds an Appender to the LoggerConfig.
    187         *
    188         * @param appender The Appender to add.
    189         * @param level The Level to use.
    190         * @param filter A Filter for the Appender reference.
    191         */
    192        public void addAppender(final Appender appender, final Level level,
    193                final Filter filter) {
    194            appenders.put(appender.getName(), new AppenderControl(appender, level,
    195                    filter));
    196        }
    197
    198        /**
    199         * Removes the Appender with the specific name.
    200         *
    201         * @param name The name of the Appender.
    202         */
    203        public void removeAppender(final String name) {
    204            final AppenderControl ctl = appenders.remove(name);
    205            if (ctl != null) {
    206                cleanupFilter(ctl);
    207            }
    208        }
    209
    210        /**
    211         * Returns all Appenders as a Map.
    212         *
    213         * @return a Map with the Appender name as the key and the Appender as the
    214         *         value.
    215         */
    216        public Map<String, Appender> getAppenders() {
    217            final Map<String, Appender> map = new HashMap<String, Appender>();
    218            for (final Map.Entry<String, AppenderControl> entry : appenders
    219                    .entrySet()) {
    220                map.put(entry.getKey(), entry.getValue().getAppender());
    221            }
    222            return map;
    223        }
    224
    225        /**
    226         * Removes all Appenders.
    227         */
    228        protected void clearAppenders() {
    229            waitForCompletion();
    230            final Collection<AppenderControl> controls = appenders.values();
    231            final Iterator<AppenderControl> iterator = controls.iterator();
    232            while (iterator.hasNext()) {
    233                final AppenderControl ctl = iterator.next();
    234                iterator.remove();
    235                cleanupFilter(ctl);
    236            }
    237        }
    238
    239        private void cleanupFilter(final AppenderControl ctl) {
    240            final Filter filter = ctl.getFilter();
    241            if (filter != null) {
    242                ctl.removeFilter(filter);
    243                filter.stop();
    244            }
    245        }
    246
    247        /**
    248         * Returns the Appender references.
    249         *
    250         * @return a List of all the Appender names attached to this LoggerConfig.
    251         */
    252        public List<AppenderRef> getAppenderRefs() {
    253            return appenderRefs;
    254        }
    255
    256        /**
    257         * Sets the logging Level.
    258         *
    259         * @param level The logging Level.
    260         */
    261        public void setLevel(final Level level) {
    262            this.level = level;
    263        }
    264
    265        /**
    266         * Returns the logging Level.
    267         *
    268         * @return the logging Level.
    269         */
    270        public Level getLevel() {
    271            return level == null ? parent.getLevel() : level;
    272        }
    273
    274        /**
    275         * Returns the LogEventFactory.
    276         *
    277         * @return the LogEventFactory.
    278         */
    279        public LogEventFactory getLogEventFactory() {
    280            return logEventFactory;
    281        }
    282
    283        /**
    284         * Sets the LogEventFactory. Usually the LogEventFactory will be this
    285         * LoggerConfig.
    286         *
    287         * @param logEventFactory the LogEventFactory.
    288         */
    289        public void setLogEventFactory(final LogEventFactory logEventFactory) {
    290            this.logEventFactory = logEventFactory;
    291        }
    292
    293        /**
    294         * Returns the valid of the additive flag.
    295         *
    296         * @return true if the LoggerConfig is additive, false otherwise.
    297         */
    298        public boolean isAdditive() {
    299            return additive;
    300        }
    301
    302        /**
    303         * Sets the additive setting.
    304         *
    305         * @param additive true if the LoggerConfig should be additive, false
    306         *            otherwise.
    307         */
    308        public void setAdditive(final boolean additive) {
    309            this.additive = additive;
    310        }
    311
    312        /**
    313         * Returns the value of logger configuration attribute {@code includeLocation},
    314         * or, if no such attribute was configured, {@code true} if logging is
    315         * synchronous or {@code false} if logging is asynchronous.
    316         *
    317         * @return whether location should be passed downstream
    318         */
    319        public boolean isIncludeLocation() {
    320            return includeLocation;
    321        }
    322
    323        /**
    324         * Returns an unmodifiable map with the configuration properties, or
    325         * {@code null} if this {@code LoggerConfig} does not have any configuration
    326         * properties.
    327         * <p>
    328         * For each {@code Property} key in the map, the value is {@code true} if
    329         * the property value has a variable that needs to be substituted.
    330         *
    331         * @return an unmodifiable map with the configuration properties, or
    332         *         {@code null}
    333         * @see Configuration#getStrSubstitutor()
    334         * @see StrSubstitutor
    335         */
    336        // LOG4J2-157
    337        public Map<Property, Boolean> getProperties() {
    338            return properties == null ? null : Collections
    339                    .unmodifiableMap(properties);
    340        }
    341
    342        /**
    343         * Logs an event.
    344         *
    345         * @param loggerName The name of the Logger.
    346         * @param fqcn The fully qualified class name of the caller.
    347         * @param marker A Marker or null if none is present.
    348         * @param level The event Level.
    349         * @param data The Message.
    350         * @param t A Throwable or null.
    351         */
    352        public void log(final String loggerName, final String fqcn,
    353                final Marker marker, final Level level, final Message data,
    354                final Throwable t) {
    355            List<Property> props = null;
    356            if (properties != null) {
    357                props = new ArrayList<Property>(properties.size());
    358
    359                for (final Map.Entry<Property, Boolean> entry : properties.entrySet()) {
    360                    final Property prop = entry.getKey();
    361                    final String value = entry.getValue() ? config.getStrSubstitutor()
    362                            .replace(prop.getValue()) : prop.getValue();
    363                    props.add(Property.createProperty(prop.getName(), value));
    364                }
    365            }
    366            final LogEvent event = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
    367            log(event);
    368        }
    369
    370        /**
    371         * Waits for all log events to complete before shutting down this
    372         * loggerConfig.
    373         */
    374        private void waitForCompletion() {
    375            shutdownLock.lock();
    376            try {
    377                if (shutdown.compareAndSet(false, true)) {
    378                    int retries = 0;
    379                    while (counter.get() > 0) {
    380                        try {
    381                            noLogEvents.await(retries + 1, TimeUnit.SECONDS);
    382                        } catch (final InterruptedException ie) {
    383                            if (++retries > MAX_RETRIES) {
    384                                break;
    385                            }
    386                        }
    387                    }
    388                }
    389            } finally {
    390                shutdownLock.unlock();
    391            }
    392        }
    393
    394        /**
    395         * Logs an event.
    396         *
    397         * @param event The log event.
    398         */
    399        public void log(final LogEvent event) {
    400
    401            counter.incrementAndGet();
    402            try {
    403                if (isFiltered(event)) {
    404                    return;
    405                }
    406
    407                event.setIncludeLocation(isIncludeLocation());
    408
    409                callAppenders(event);
    410
    411                if (additive && parent != null) {
    412                    parent.log(event);
    413                }
    414            } finally {
    415                if (counter.decrementAndGet() == 0) {
    416                    shutdownLock.lock();
    417                    try {
    418                        if (shutdown.get()) {
    419                            noLogEvents.signalAll();
    420                        }
    421                    } finally {
    422                        shutdownLock.unlock();
    423                    }
    424                }
    425            }
    426        }
    427
    428        protected void callAppenders(final LogEvent event) {
    429            for (final AppenderControl control : appenders.values()) {
    430                control.callAppender(event);
    431            }
    432        }
    433
    434
    435        @Override
    436        public String toString() {
    437            return Strings.isEmpty(name) ? "root" : name;
    438        }
    439
    440        /**
    441         * Factory method to create a LoggerConfig.
    442         *
    443         * @param additivity True if additive, false otherwise.
    444         * @param level The Level to be associated with the Logger.
    445         * @param loggerName The name of the Logger.
    446         * @param includeLocation whether location should be passed downstream
    447         * @param refs An array of Appender names.
    448         * @param properties Properties to pass to the Logger.
    449         * @param config The Configuration.
    450         * @param filter A Filter.
    451         * @return A new LoggerConfig.
    452         */
    453        @PluginFactory
    454        public static LoggerConfig createLogger(
    455                @PluginAttribute("additivity") final String additivity,
    456                @PluginAttribute("level") final Level level,
    457                @PluginAttribute("name") final String loggerName,
    458                @PluginAttribute("includeLocation") final String includeLocation,
    459                @PluginElement("AppenderRef") final AppenderRef[] refs,
    460                @PluginElement("Properties") final Property[] properties,
    461                @PluginConfiguration final Configuration config,
    462                @PluginElement("Filter") final Filter filter) {
    463            if (loggerName == null) {
    464                LOGGER.error("Loggers cannot be configured without a name");
    465                return null;
    466            }
    467
    468            final List<AppenderRef> appenderRefs = Arrays.asList(refs);
    469            final String name = loggerName.equals("root") ? Strings.EMPTY : loggerName;
    470            final boolean additive = Booleans.parseBoolean(additivity, true);
    471
    472            return new LoggerConfig(name, appenderRefs, filter, level, additive,
    473                    properties, config, includeLocation(includeLocation));
    474        }
    475
    476        // Note: for asynchronous loggers, includeLocation default is FALSE,
    477        // for synchronous loggers, includeLocation default is TRUE.
    478        protected static boolean includeLocation(final String includeLocationConfigValue) {
    479            if (includeLocationConfigValue == null) {
    480                final boolean sync = !AsyncLoggerContextSelector.class.getName()
    481                        .equals(PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR));
    482                return sync;
    483            }
    484            return Boolean.parseBoolean(includeLocationConfigValue);
    485        }
    486
    487        /**
    488         * The root Logger.
    489         */
    490        @Plugin(name = "root", category = "Core", printObject = true)
    491        public static class RootLogger extends LoggerConfig {
    492
    493            private static final long serialVersionUID = 1L;
    494
    495            @PluginFactory
    496            public static LoggerConfig createLogger(
    497                    @PluginAttribute("additivity") final String additivity,
    498                    @PluginAttribute("level") final Level level,
    499                    @PluginAttribute("includeLocation") final String includeLocation,
    500                    @PluginElement("AppenderRef") final AppenderRef[] refs,
    501                    @PluginElement("Properties") final Property[] properties,
    502                    @PluginConfiguration final Configuration config,
    503                    @PluginElement("Filter") final Filter filter) {
    504                final List<AppenderRef> appenderRefs = Arrays.asList(refs);
    505                final Level actualLevel = level == null ? Level.ERROR : level;
    506                final boolean additive = Booleans.parseBoolean(additivity, true);
    507
    508                return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs,
    509                        filter, actualLevel, additive, properties, config,
    510                        includeLocation(includeLocation));
    511            }
    512        }
    513
    514    }