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;
018    
019    import java.io.Serializable;
020    import java.util.ArrayList;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.logging.log4j.Level;
026    import org.apache.logging.log4j.Marker;
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.filter.CompositeFilter;
030    import org.apache.logging.log4j.message.Message;
031    import org.apache.logging.log4j.message.MessageFactory;
032    import org.apache.logging.log4j.message.SimpleMessage;
033    import org.apache.logging.log4j.spi.AbstractLogger;
034    import 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     */
049    public 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 config The new Configuration.
243         */
244        void updateConfiguration(final Configuration config) {
245            this.config = new PrivateConfig(config, 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    
301                return intLevel >= level.intLevel();
302            }
303    
304            boolean filter(final Level level, final Marker marker, final String msg, final Throwable t) {
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, t);
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 String msg, final Object... p1) {
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, p1);
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 Object 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            boolean filter(final Level level, final Marker marker, final Message msg, final Throwable t) {
344                config.getConfigurationMonitor().checkConfiguration();
345                final Filter filter = config.getFilter();
346                if (filter != null) {
347                    final Filter.Result r = filter.filter(logger, level, marker, msg, t);
348                    if (r != Filter.Result.NEUTRAL) {
349                        return r == Filter.Result.ACCEPT;
350                    }
351                }
352    
353                return intLevel >= level.intLevel();
354            }
355        }
356    
357        /**
358         * Returns a String representation of this instance in the form {@code "name:level[ in context_name]"}.
359         * @return A String describing this Logger instance.
360         */
361        @Override
362        public String toString() {
363            final String nameLevel = Strings.EMPTY + getName() + ':' + getLevel();
364            if (context == null) {
365                return nameLevel;
366            }
367            final String contextName = context.getName();
368            return contextName == null ? nameLevel : nameLevel + " in " + contextName;
369        }
370    }