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 */
017package org.apache.logging.log4j.core;
018
019import java.io.Serializable;
020import java.util.ArrayList;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.logging.log4j.Level;
026import org.apache.logging.log4j.Marker;
027import org.apache.logging.log4j.core.config.Configuration;
028import org.apache.logging.log4j.core.config.LoggerConfig;
029import org.apache.logging.log4j.core.filter.CompositeFilter;
030import org.apache.logging.log4j.message.Message;
031import org.apache.logging.log4j.message.MessageFactory;
032import org.apache.logging.log4j.message.SimpleMessage;
033import org.apache.logging.log4j.spi.AbstractLogger;
034import org.apache.logging.log4j.util.Strings;
035
036/**
037 * The core implementation of the {@link org.apache.logging.log4j.Logger} interface. Besides providing an
038 * implementation of all the Logger methods, this class also provides some convenience methods for Log4j 1.x
039 * compatibility as well as access to the {@link org.apache.logging.log4j.core.Filter Filters} and
040 * {@link org.apache.logging.log4j.core.Appender Appenders} associated with this Logger. Note that access to these
041 * underlying objects is provided primarily for use in unit tests or bridging legacy Log4j 1.x code. Future versions
042 * of this class may or may not include the various methods that are noted as not being part of the public API.
043 *
044 * TODO All the isEnabled methods could be pushed into a filter interface.  Not sure of the utility of having
045 * isEnabled be able to examine the message pattern and parameters. (RG) Moving the isEnabled methods out of
046 * Logger noticeably impacts performance. The message pattern and parameters are required so that they can be
047 * used in global filters.
048 */
049public class Logger extends AbstractLogger {
050
051    private static final long serialVersionUID = 1L;
052
053    /**
054     * Config should be consistent across threads.
055     */
056    protected volatile PrivateConfig config;
057
058    // FIXME: ditto to the above
059    private final LoggerContext context;
060
061    /**
062     * The constructor.
063     * @param context The LoggerContext this Logger is associated with.
064     * @param messageFactory The message factory.
065     * @param name The name of the Logger.
066     */
067    protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {
068        super(name, messageFactory);
069        this.context = context;
070        config = new PrivateConfig(context.getConfiguration(), this);
071    }
072
073    /**
074     * This method is only used for 1.x compatibility.
075     * Returns the parent of this Logger. If it doesn't already exist return a temporary Logger.
076     * @return The parent Logger.
077     */
078    public Logger getParent() {
079        final LoggerConfig lc = config.loggerConfig.getName().equals(getName()) ? config.loggerConfig.getParent() :
080            config.loggerConfig;
081        if (lc == null) {
082            return null;
083        }
084        if (context.hasLogger(lc.getName())) {
085            return context.getLogger(lc.getName(), getMessageFactory());
086        }
087        return new Logger(context, lc.getName(), this.getMessageFactory());
088    }
089
090    /**
091     * Returns the LoggerContext this Logger is associated with.
092     * @return the LoggerContext.
093     */
094    public LoggerContext getContext() {
095        return context;
096    }
097
098    /**
099     * This method is not exposed through the public API and is provided primarily for unit testing.
100     * @param level The Level to use on this Logger.
101     */
102    public synchronized void setLevel(final Level level) {
103        if (level != null) {
104            config = new PrivateConfig(config, level);
105        }
106    }
107
108    @Override
109    public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message, final Throwable t) {
110        final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message;
111        config.config.getConfigurationMonitor().checkConfiguration();
112        config.loggerConfig.log(getName(), fqcn, marker, level, msg, t);
113    }
114
115    @Override
116    public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
117        return config.filter(level, marker, message, t);
118    }
119
120    @Override
121    public boolean isEnabled(final Level level, final Marker marker, final String message) {
122        return config.filter(level, marker, message);
123    }
124
125    @Override
126    public boolean isEnabled(final Level level, final Marker marker, final String message, final Object... params) {
127        return config.filter(level, marker, message, params);
128    }
129
130    @Override
131    public boolean isEnabled(final Level level, final Marker marker, final Object message, final Throwable t) {
132        return config.filter(level, marker, message, t);
133    }
134
135    @Override
136    public boolean isEnabled(final Level level, final Marker marker, final Message message, final Throwable t) {
137        return config.filter(level, marker, message, t);
138    }
139
140    /**
141     * This method is not exposed through the public API and is used primarily for unit testing.
142     * @param appender The Appender to add to the Logger.
143     */
144    public void addAppender(final Appender appender) {
145        config.config.addLoggerAppender(this, appender);
146    }
147
148    /**
149     * This method is not exposed through the public API and is used primarily for unit testing.
150     * @param appender The Appender to remove from the Logger.
151     */
152    public void removeAppender(final Appender appender) {
153        config.loggerConfig.removeAppender(appender.getName());
154    }
155
156    /**
157     * This method is not exposed through the public API and is used primarily for unit testing.
158     * @return A Map containing the Appender's name as the key and the Appender as the value.
159     */
160    public Map<String, Appender> getAppenders() {
161         return config.loggerConfig.getAppenders();
162    }
163
164    /**
165     * This method is not exposed through the public API and is used primarily for unit testing.
166     * @return An Iterator over all the Filters associated with the Logger.
167     */
168    // FIXME: this really ought to be an Iterable instead of an Iterator
169    public Iterator<Filter> getFilters() {
170        final Filter filter = config.loggerConfig.getFilter();
171        if (filter == null) {
172            return new ArrayList<Filter>().iterator();
173        } else if (filter instanceof CompositeFilter) {
174            return ((CompositeFilter) filter).iterator();
175        } else {
176            final List<Filter> filters = new ArrayList<Filter>();
177            filters.add(filter);
178            return filters.iterator();
179        }
180    }
181
182    /**
183     * Gets the Level associated with the Logger.
184     *
185     * @return the Level associate with the Logger.
186     */
187    @Override
188    public Level getLevel() {
189        return config.level;
190    }
191
192    /**
193     * This method is not exposed through the public API and is used primarily for unit testing.
194     * @return The number of Filters associated with the Logger.
195     */
196    public int filterCount() {
197        final Filter filter = config.loggerConfig.getFilter();
198        if (filter == null) {
199            return 0;
200        } else if (filter instanceof CompositeFilter) {
201            return ((CompositeFilter) filter).size();
202        }
203        return 1;
204    }
205
206    /**
207     * This method is not exposed through the public API and is used primarily for unit testing.
208     * @param filter The Filter to add.
209     */
210    public void addFilter(final Filter filter) {
211        config.config.addLoggerFilter(this, filter);
212    }
213
214    /**
215     * This method is not exposed through the public API and is present only to support the Log4j 1.2
216     * compatibility bridge.
217     * @return true if the associated LoggerConfig is additive, false otherwise.
218     */
219    public boolean isAdditive() {
220        return config.loggerConfig.isAdditive();
221    }
222
223    /**
224     * This method is not exposed through the public API and is present only to support the Log4j 1.2
225     * compatibility bridge.
226     * @param additive Boolean value to indicate whether the Logger is additive or not.
227     */
228    public void setAdditive(final boolean additive) {
229        config.config.setLoggerAdditive(this, additive);
230    }
231
232    /**
233     * Associates the Logger with a new Configuration. This method is not exposed through the
234     * public API.
235     *
236     * There are two ways that could be used to guarantee all threads are aware of changes to
237     * config. 1. synchronize this method. Accessors don't need to be synchronized as Java will
238     * treat all variables within a synchronized block as volatile. 2. Declare the variable
239     * volatile. Option 2 is used here as the performance cost is very low and it does a better
240     * job at documenting how it is used.
241     *
242     * @param newConfig The new Configuration.
243     */
244    protected void updateConfiguration(final Configuration newConfig) {
245        this.config = new PrivateConfig(newConfig, this);
246    }
247
248    /**
249     * The binding between a Logger and its configuration.
250     */
251    // TODO: Should not be Serializable per EJ item 74 (2nd Ed)?
252    protected class PrivateConfig implements Serializable {
253        private static final long serialVersionUID = 1L;
254        // config fields are public to make them visible to Logger subclasses
255        public final LoggerConfig loggerConfig;
256        public final Configuration config;
257        private final Level level;
258        private final int intLevel;
259        private final Logger logger;
260
261        public PrivateConfig(final Configuration config, final Logger logger) {
262            this.config = config;
263            this.loggerConfig = config.getLoggerConfig(getName());
264            this.level = this.loggerConfig.getLevel();
265            this.intLevel = this.level.intLevel();
266            this.logger = logger;
267        }
268
269        public PrivateConfig(final PrivateConfig pc, final Level level) {
270            this.config = pc.config;
271            this.loggerConfig = pc.loggerConfig;
272            this.level = level;
273            this.intLevel = this.level.intLevel();
274            this.logger = pc.logger;
275        }
276
277        public PrivateConfig(final PrivateConfig pc, final LoggerConfig lc) {
278            this.config = pc.config;
279            this.loggerConfig = lc;
280            this.level = lc.getLevel();
281            this.intLevel = this.level.intLevel();
282            this.logger = pc.logger;
283        }
284
285        // LOG4J2-151: changed visibility to public
286        public void logEvent(final LogEvent event) {
287            config.getConfigurationMonitor().checkConfiguration();
288            loggerConfig.log(event);
289        }
290
291        boolean filter(final Level level, final Marker marker, final String msg) {
292            config.getConfigurationMonitor().checkConfiguration();
293            final Filter filter = config.getFilter();
294            if (filter != null) {
295                final Filter.Result r = filter.filter(logger, level, marker, msg);
296                if (r != Filter.Result.NEUTRAL) {
297                    return r == Filter.Result.ACCEPT;
298                }
299            }
300            return level != null && intLevel >= level.intLevel();
301        }
302
303        boolean filter(final Level level, final Marker marker, final String msg, final Throwable t) {
304            config.getConfigurationMonitor().checkConfiguration();
305            final Filter filter = config.getFilter();
306            if (filter != null) {
307                final Filter.Result r = filter.filter(logger, level, marker, msg, t);
308                if (r != Filter.Result.NEUTRAL) {
309                    return r == Filter.Result.ACCEPT;
310                }
311            }
312            return level != null && intLevel >= level.intLevel();
313        }
314
315        boolean filter(final Level level, final Marker marker, final String msg, final Object... p1) {
316            config.getConfigurationMonitor().checkConfiguration();
317            final Filter filter = config.getFilter();
318            if (filter != null) {
319                final Filter.Result r = filter.filter(logger, level, marker, msg, p1);
320                if (r != Filter.Result.NEUTRAL) {
321                    return r == Filter.Result.ACCEPT;
322                }
323            }
324            return level != null && intLevel >= level.intLevel();
325        }
326
327        boolean filter(final Level level, final Marker marker, final Object msg, final Throwable t) {
328            config.getConfigurationMonitor().checkConfiguration();
329            final Filter filter = config.getFilter();
330            if (filter != null) {
331                final Filter.Result r = filter.filter(logger, level, marker, msg, t);
332                if (r != Filter.Result.NEUTRAL) {
333                    return r == Filter.Result.ACCEPT;
334                }
335            }
336            return level != null && intLevel >= level.intLevel();
337        }
338
339        boolean filter(final Level level, final Marker marker, final Message msg, final Throwable t) {
340            config.getConfigurationMonitor().checkConfiguration();
341            final Filter filter = config.getFilter();
342            if (filter != null) {
343                final Filter.Result r = filter.filter(logger, level, marker, msg, t);
344                if (r != Filter.Result.NEUTRAL) {
345                    return r == Filter.Result.ACCEPT;
346                }
347            }
348            return level != null && intLevel >= level.intLevel();
349        }
350    }
351
352    /**
353     * Returns a String representation of this instance in the form {@code "name:level[ in context_name]"}.
354     * @return A String describing this Logger instance.
355     */
356    @Override
357    public String toString() {
358        final String nameLevel = Strings.EMPTY + getName() + ':' + getLevel();
359        if (context == null) {
360            return nameLevel;
361        }
362        final String contextName = context.getName();
363        return contextName == null ? nameLevel : nameLevel + " in " + contextName;
364    }
365}