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