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.LoggerContext;
028    import org.apache.logging.log4j.core.LoggerContext.Status;
029    import org.apache.logging.log4j.core.config.AppenderRef;
030    import org.apache.logging.log4j.core.config.Configuration;
031    import org.apache.logging.log4j.core.config.LoggerConfig;
032    import org.apache.logging.log4j.core.config.Property;
033    import org.apache.logging.log4j.core.config.plugins.Plugin;
034    import org.apache.logging.log4j.core.config.plugins.PluginAttr;
035    import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
036    import org.apache.logging.log4j.core.config.plugins.PluginElement;
037    import org.apache.logging.log4j.core.config.plugins.PluginFactory;
038    import org.apache.logging.log4j.status.StatusLogger;
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 FastFileAppender or
062     * FastRollingFileAppender, 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", type = "Core", printObject = true)
069    public class AsyncLoggerConfig extends LoggerConfig {
070    
071        private static final Logger LOGGER = StatusLogger.getLogger();
072        private AsyncLoggerConfigHelper helper;
073    
074        /**
075         * Default constructor.
076         */
077        public AsyncLoggerConfig() {
078            super();
079        }
080    
081        /**
082         * Constructor that sets the name, level and additive values.
083         * 
084         * @param name The Logger name.
085         * @param level The Level.
086         * @param additive true if the Logger is additive, false otherwise.
087         */
088        public AsyncLoggerConfig(final String name, final Level level,
089                final boolean additive) {
090            super(name, level, additive);
091        }
092    
093        protected AsyncLoggerConfig(final String name,
094                final List<AppenderRef> appenders, final Filter filter,
095                final Level level, final boolean additive,
096                final Property[] properties, final Configuration config,
097                final boolean includeLocation) {
098            super(name, appenders, filter, level, additive, properties, config,
099                    includeLocation);
100        }
101    
102        /**
103         * Passes on the event to a separate thread that will call
104         * {@link #asyncCallAppenders(LogEvent)}.
105         */
106        @Override
107        protected void callAppenders(LogEvent event) {
108            // populate lazily initialized fields
109            event.getSource();
110            event.getThreadName();
111    
112            // pass on the event to a separate thread
113            helper.callAppendersFromAnotherThread(event);
114        }
115    
116        /** Called by AsyncLoggerConfigHelper.RingBufferLog4jEventHandler. */
117        void asyncCallAppenders(LogEvent event) {
118            super.callAppenders(event);
119        }
120    
121        @Override
122        public void startFilter() {
123            if (helper == null) {
124                helper = new AsyncLoggerConfigHelper(this);
125            }
126            super.startFilter();
127        }
128    
129        @Override
130        public void stopFilter() {
131            // only stop disruptor if shutting down logging subsystem
132            if (LogManager.getContext() instanceof LoggerContext) {
133                if (((LoggerContext) LogManager.getContext()).getStatus() != Status.STOPPING) {
134                    return;
135                }
136            }
137            helper.shutdown();
138            super.stopFilter();
139        }
140    
141        /**
142         * Factory method to create a LoggerConfig.
143         * 
144         * @param additivity True if additive, false otherwise.
145         * @param levelName The Level to be associated with the Logger.
146         * @param loggerName The name of the Logger.
147         * @param includeLocation "true" if location should be passed downstream
148         * @param refs An array of Appender names.
149         * @param properties Properties to pass to the Logger.
150         * @param config The Configuration.
151         * @param filter A Filter.
152         * @return A new LoggerConfig.
153         */
154        @PluginFactory
155        public static LoggerConfig createLogger(
156                @PluginAttr("additivity") final String additivity,
157                @PluginAttr("level") final String levelName,
158                @PluginAttr("name") final String loggerName,
159                @PluginAttr("includeLocation") final String includeLocation,
160                @PluginElement("appender-ref") final AppenderRef[] refs,
161                @PluginElement("properties") final Property[] properties,
162                @PluginConfiguration final Configuration config,
163                @PluginElement("filters") final Filter filter) {
164            if (loggerName == null) {
165                LOGGER.error("Loggers cannot be configured without a name");
166                return null;
167            }
168    
169            final List<AppenderRef> appenderRefs = Arrays.asList(refs);
170            Level level;
171            try {
172                level = Level.toLevel(levelName, Level.ERROR);
173            } catch (final Exception ex) {
174                LOGGER.error(
175                        "Invalid Log level specified: {}. Defaulting to Error",
176                        levelName);
177                level = Level.ERROR;
178            }
179            final String name = loggerName.equals("root") ? "" : loggerName;
180            final boolean additive = additivity == null ? true : Boolean
181                    .parseBoolean(additivity);
182    
183            return new AsyncLoggerConfig(name, appenderRefs, filter, level,
184                    additive, properties, config, includeLocation(includeLocation));
185        }
186    
187        // Note: for asynchronous loggers, includeLocation default is FALSE
188        private static boolean includeLocation(String includeLocationConfigValue) {
189            if (includeLocationConfigValue == null) {
190                return false;
191            }
192            return Boolean.parseBoolean(includeLocationConfigValue);
193        }
194    
195        /**
196         * An asynchronous root Logger.
197         */
198        @Plugin(name = "asyncRoot", type = "Core", printObject = true)
199        public static class RootLogger extends LoggerConfig {
200    
201            @PluginFactory
202            public static LoggerConfig createLogger(
203                    @PluginAttr("additivity") final String additivity,
204                    @PluginAttr("level") final String levelName,
205                    @PluginAttr("includeLocation") final String includeLocation,
206                    @PluginElement("appender-ref") final AppenderRef[] refs,
207                    @PluginElement("properties") final Property[] properties,
208                    @PluginConfiguration final Configuration config,
209                    @PluginElement("filters") final Filter filter) {
210                final List<AppenderRef> appenderRefs = Arrays.asList(refs);
211                Level level;
212                try {
213                    level = Level.toLevel(levelName, Level.ERROR);
214                } catch (final Exception ex) {
215                    LOGGER.error(
216                            "Invalid Log level specified: {}. Defaulting to Error",
217                            levelName);
218                    level = Level.ERROR;
219                }
220                final boolean additive = additivity == null ? true : Boolean
221                        .parseBoolean(additivity);
222    
223                return new AsyncLoggerConfig(LogManager.ROOT_LOGGER_NAME,
224                        appenderRefs, filter, level, additive, properties, config,
225                        includeLocation(includeLocation));
226            }
227        }
228    }