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