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                        includeLocation(includeLocation));
240            }
241        }
242    }