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