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