View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core;
18  
19  import java.io.Serializable;
20  import java.util.ArrayList;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.logging.log4j.Level;
26  import org.apache.logging.log4j.Marker;
27  import org.apache.logging.log4j.core.config.Configuration;
28  import org.apache.logging.log4j.core.config.LoggerConfig;
29  import org.apache.logging.log4j.core.filter.CompositeFilter;
30  import org.apache.logging.log4j.message.Message;
31  import org.apache.logging.log4j.message.MessageFactory;
32  import org.apache.logging.log4j.message.SimpleMessage;
33  import org.apache.logging.log4j.spi.AbstractLogger;
34  import org.apache.logging.log4j.util.Strings;
35  
36  /**
37   * The core implementation of the {@link org.apache.logging.log4j.Logger} interface. Besides providing an
38   * implementation of all the Logger methods, this class also provides some convenience methods for Log4j 1.x
39   * compatibility as well as access to the {@link org.apache.logging.log4j.core.Filter Filters} and
40   * {@link org.apache.logging.log4j.core.Appender Appenders} associated with this Logger. Note that access to these
41   * underlying objects is provided primarily for use in unit tests or bridging legacy Log4j 1.x code. Future versions
42   * of this class may or may not include the various methods that are noted as not being part of the public API.
43   *
44   * TODO All the isEnabled methods could be pushed into a filter interface.  Not sure of the utility of having
45   * isEnabled be able to examine the message pattern and parameters. (RG) Moving the isEnabled methods out of
46   * Logger noticeably impacts performance. The message pattern and parameters are required so that they can be
47   * used in global filters.
48   */
49  public class Logger extends AbstractLogger {
50  
51      private static final long serialVersionUID = 1L;
52  
53      /**
54       * Config should be consistent across threads.
55       */
56      protected volatile PrivateConfig config;
57  
58      // FIXME: ditto to the above
59      private final LoggerContext context;
60  
61      /**
62       * The constructor.
63       * @param context The LoggerContext this Logger is associated with.
64       * @param messageFactory The message factory.
65       * @param name The name of the Logger.
66       */
67      protected Logger(final LoggerContext context, final String name, final MessageFactory messageFactory) {
68          super(name, messageFactory);
69          this.context = context;
70          config = new PrivateConfig(context.getConfiguration(), this);
71      }
72  
73      /**
74       * This method is only used for 1.x compatibility.
75       * Returns the parent of this Logger. If it doesn't already exist return a temporary Logger.
76       * @return The parent Logger.
77       */
78      public Logger getParent() {
79          final LoggerConfig lc = config.loggerConfig.getName().equals(getName()) ? config.loggerConfig.getParent() :
80              config.loggerConfig;
81          if (lc == null) {
82              return null;
83          }
84          if (context.hasLogger(lc.getName())) {
85              return context.getLogger(lc.getName(), getMessageFactory());
86          }
87          return new Logger(context, lc.getName(), this.getMessageFactory());
88      }
89  
90      /**
91       * Returns the LoggerContext this Logger is associated with.
92       * @return the LoggerContext.
93       */
94      public LoggerContext getContext() {
95          return context;
96      }
97  
98      /**
99       * 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 }