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