    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.async;
    018
    019    import java.util.Arrays;
    020    import java.util.List;
    021
    022    import org.apache.logging.log4j.Level;
    023    import org.apache.logging.log4j.LogManager;
    024    import org.apache.logging.log4j.core.Filter;
    025    import org.apache.logging.log4j.core.LogEvent;
    026    import org.apache.logging.log4j.core.config.AppenderRef;
    027    import org.apache.logging.log4j.core.config.Configuration;
    028    import org.apache.logging.log4j.core.config.LoggerConfig;
    029    import org.apache.logging.log4j.core.config.Node;
    030    import org.apache.logging.log4j.core.config.Property;
    031    import org.apache.logging.log4j.core.config.plugins.Plugin;
    032    import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
    033    import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
    034    import org.apache.logging.log4j.core.config.plugins.PluginElement;
    035    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
    036    import org.apache.logging.log4j.core.jmx.RingBufferAdmin;
    037    import org.apache.logging.log4j.core.util.Booleans;
    038    import org.apache.logging.log4j.util.Strings;
    039
    040    /**
    041     * Asynchronous Logger object that is created via configuration and can be
    042     * combined with synchronous loggers.
    043     * <p>
    044     * AsyncLoggerConfig is a logger designed for high throughput and low latency
    045     * logging. It does not perform any I/O in the calling (application) thread, but
    046     * instead hands off the work to another thread as soon as possible. The actual
    047     * logging is performed in the background thread. It uses the LMAX Disruptor
    048     * library for inter-thread communication. (<a
    049     * href="http://lmax-exchange.github.com/disruptor/"
    050     * >http://lmax-exchange.github.com/disruptor/</a>)
    051     * <p>
    052     * To use AsyncLoggerConfig, specify {@code <asyncLogger>} or
    053     * {@code <asyncRoot>} in configuration.
    054     * <p>
    055     * Note that for performance reasons, this logger does not include source
    056     * location by default. You need to specify {@code includeLocation="true"} in
    057     * the configuration or any %class, %location or %line conversion patterns in
    058     * your log4j.xml configuration will produce either a "?" character or no output
    059     * at all.
    060     * <p>
    061     * For best performance, use AsyncLoggerConfig with the RandomAccessFileAppender or
    062     * RollingRandomAccessFileAppender, with immediateFlush=false. These appenders have
    063     * built-in support for the batching mechanism used by the Disruptor library,
    064     * and they will flush to disk at the end of each batch. This means that even
    065     * with immediateFlush=false, there will never be any items left in the buffer;
    066     * all log events will all be written to disk in a very efficient manner.
    067     */
    068    @Plugin(name = "asyncLogger", category = Node.CATEGORY, printObject = true)
    069    public class AsyncLoggerConfig extends LoggerConfig {
    070
    071        private static final long serialVersionUID = 1L;
    072
    073        private AsyncLoggerConfigHelper helper;
    074
    075        /**
    076         * Default constructor.
    077         */
    078        public AsyncLoggerConfig() {
    079            super();
    080        }
    081
    082        /**
    083         * Constructor that sets the name, level and additive values.
    084         *
    085         * @param name The Logger name.
    086         * @param level The Level.
    087         * @param additive true if the Logger is additive, false otherwise.
    088         */
    089        public AsyncLoggerConfig(final String name, final Level level,
    090                final boolean additive) {
    091            super(name, level, additive);
    092        }
    093
    094        protected AsyncLoggerConfig(final String name,
    095                final List<AppenderRef> appenders, final Filter filter,
    096                final Level level, final boolean additive,
    097                final Property[] properties, final Configuration config,
    098                final boolean includeLocation) {
    099            super(name, appenders, filter, level, additive, properties, config,
    100                    includeLocation);
    101        }
    102
    103        /**
    104         * Passes on the event to a separate thread that will call
    105         * {@link #asyncCallAppenders(LogEvent)}.
    106         */
    107        @Override
    108        protected void callAppenders(final LogEvent event) {
    109            // populate lazily initialized fields
    110            event.getSource();
    111            event.getThreadName();
    112
    113            // pass on the event to a separate thread
    114            if (!helper.callAppendersFromAnotherThread(event)) {
    115                super.callAppenders(event);
    116            }
    117        }
    118
    119        /** Called by AsyncLoggerConfigHelper.RingBufferLog4jEventHandler. */
    120        void asyncCallAppenders(final LogEvent event) {
    121            super.callAppenders(event);
    122        }
    123
    124        private String displayName() {
    125            return LogManager.ROOT_LOGGER_NAME.equals(getName()) ? "root" : getName();
    126        }
    127
    128        @Override
    129        public void start() {
    130            LOGGER.trace("AsyncLoggerConfig[{}] starting...", displayName());
    131            this.setStarting();
    132            if (helper == null) {
    133                helper = new AsyncLoggerConfigHelper(this);
    134            } else {
    135                AsyncLoggerConfigHelper.claim(); // LOG4J2-336
    136            }
    137            super.start();
    138        }
    139
    140        @Override
    141        public void stop() {
    142            LOGGER.trace("AsyncLoggerConfig[{}] stopping...", displayName());
    143            this.setStopping();
    144            AsyncLoggerConfigHelper.release();
    145            super.stop();
    146        }
    147
    148        /**
    149         * Creates and returns a new {@code RingBufferAdmin} that instruments the
    150         * ringbuffer of this {@code AsyncLoggerConfig}.
    151         *
    152         * @param contextName name of the {@code LoggerContext}
    153         */
    154        public RingBufferAdmin createRingBufferAdmin(final String contextName) {
    155            return helper.createRingBufferAdmin(contextName, getName());
    156        }
    157
    158        /**
    159         * Factory method to create a LoggerConfig.
    160         *
    161         * @param additivity True if additive, false otherwise.
    162         * @param levelName The Level to be associated with the Logger.
    163         * @param loggerName The name of the Logger.
    164         * @param includeLocation "true" if location should be passed downstream
    165         * @param refs An array of Appender names.
    166         * @param properties Properties to pass to the Logger.
    167         * @param config The Configuration.
    168         * @param filter A Filter.
    169         * @return A new LoggerConfig.
    170         */
    171        @PluginFactory
    172        public static LoggerConfig createLogger(
    173                @PluginAttribute("additivity") final String additivity,
    174                @PluginAttribute("level") final String levelName,
    175                @PluginAttribute("name") final String loggerName,
    176                @PluginAttribute("includeLocation") final String includeLocation,
    177                @PluginElement("AppenderRef") final AppenderRef[] refs,
    178                @PluginElement("Properties") final Property[] properties,
    179                @PluginConfiguration final Configuration config,
    180                @PluginElement("Filter") final Filter filter) {
    181            if (loggerName == null) {
    182                LOGGER.error("Loggers cannot be configured without a name");
    183                return null;
    184            }
    185
    186            final List<AppenderRef> appenderRefs = Arrays.asList(refs);
    187            Level level;
    188            try {
    189                level = Level.toLevel(levelName, Level.ERROR);
    190            } catch (final Exception ex) {
    191                LOGGER.error(
    192                        "Invalid Log level specified: {}. Defaulting to Error",
    193                        levelName);
    194                level = Level.ERROR;
    195            }
    196            final String name = loggerName.equals("root") ? Strings.EMPTY : loggerName;
    197            final boolean additive = Booleans.parseBoolean(additivity, true);
    198
    199            return new AsyncLoggerConfig(name, appenderRefs, filter, level,
    200                    additive, properties, config, includeLocation(includeLocation));
    201        }
    202
    203        // Note: for asynchronous loggers, includeLocation default is FALSE
    204        protected static boolean includeLocation(final String includeLocationConfigValue) {
    205            return Boolean.parseBoolean(includeLocationConfigValue);
    206        }
    207
    208        /**
    209         * An asynchronous root Logger.
    210         */
    211        @Plugin(name = "asyncRoot", category = "Core", printObject = true)
    212        public static class RootLogger extends LoggerConfig {
    213
    214            private static final long serialVersionUID = 1L;
    215
    216            @PluginFactory
    217            public static LoggerConfig createLogger(
    218                    @PluginAttribute("additivity") final String additivity,
    219                    @PluginAttribute("level") final String levelName,
    220                    @PluginAttribute("includeLocation") final String includeLocation,
    221                    @PluginElement("AppenderRef") final AppenderRef[] refs,
    222                    @PluginElement("Properties") final Property[] properties,
    223                    @PluginConfiguration final Configuration config,
    224                    @PluginElement("Filter") final Filter filter) {
    225                final List<AppenderRef> appenderRefs = Arrays.asList(refs);
    226                Level level;
    227                try {
    228                    level = Level.toLevel(levelName, Level.ERROR);
    229                } catch (final Exception ex) {
    230                    LOGGER.error(
    231                            "Invalid Log level specified: {}. Defaulting to Error",
    232                            levelName);
    233                    level = Level.ERROR;
    234                }
    235                final boolean additive = Booleans.parseBoolean(additivity, true);
    236
    237                return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME,
    238                        appenderRefs, filter, level, additive, properties, config,
    239                        AsyncLoggerConfig.includeLocation(includeLocation));
    240            }
    241        }
    242    }