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.config;
18  
19  import org.apache.logging.log4j.Level;
20  import org.apache.logging.log4j.LogManager;
21  import org.apache.logging.log4j.Logger;
22  import org.apache.logging.log4j.Marker;
23  import org.apache.logging.log4j.core.Appender;
24  import org.apache.logging.log4j.core.Filter;
25  import org.apache.logging.log4j.core.LifeCycle;
26  import org.apache.logging.log4j.core.LogEvent;
27  import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
28  import org.apache.logging.log4j.core.config.plugins.Plugin;
29  import org.apache.logging.log4j.core.config.plugins.PluginAttr;
30  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
31  import org.apache.logging.log4j.core.config.plugins.PluginElement;
32  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
33  import org.apache.logging.log4j.core.filter.AbstractFilterable;
34  import org.apache.logging.log4j.core.helpers.Constants;
35  import org.apache.logging.log4j.core.helpers.Loader;
36  import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
37  import org.apache.logging.log4j.core.impl.LogEventFactory;
38  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
39  import org.apache.logging.log4j.message.Message;
40  import org.apache.logging.log4j.status.StatusLogger;
41  import org.apache.logging.log4j.util.PropertiesUtil;
42  
43  import java.io.Serializable;
44  import java.util.ArrayList;
45  import java.util.Arrays;
46  import java.util.Collection;
47  import java.util.Collections;
48  import java.util.HashMap;
49  import java.util.Iterator;
50  import java.util.List;
51  import java.util.Map;
52  import java.util.concurrent.ConcurrentHashMap;
53  import java.util.concurrent.atomic.AtomicInteger;
54  
55  /**
56   * Logger object that is created via configuration.
57   */
58  @Plugin(name = "logger", category = "Core", printObject = true)
59  public class LoggerConfig extends AbstractFilterable {
60  
61      private static final Logger LOGGER = StatusLogger.getLogger();
62      private static final int MAX_RETRIES = 3;
63      private static final long WAIT_TIME = 1000;
64      private static LogEventFactory LOG_EVENT_FACTORY = null;
65  
66      private List<AppenderRef> appenderRefs = new ArrayList<AppenderRef>();
67      private final Map<String, AppenderControl<?>> appenders = new ConcurrentHashMap<String, AppenderControl<?>>();
68      private final String name;
69      private LogEventFactory logEventFactory;
70      private Level level;
71      private boolean additive = true;
72      private boolean includeLocation = true;
73      private LoggerConfig parent;
74      private final AtomicInteger counter = new AtomicInteger();
75      private boolean shutdown = false;
76      private final Map<Property, Boolean> properties;
77      private final Configuration config;
78  
79      static {
80          final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY);
81          if (factory != null) {
82              try {
83                  final Class<?> clazz = Loader.loadClass(factory);
84                  if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) {
85                      LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance();
86                  }
87              } catch (final Exception ex) {
88                  LOGGER.error("Unable to create LogEventFactory " + factory, ex);
89              }
90          }
91          if (LOG_EVENT_FACTORY == null) {
92              LOG_EVENT_FACTORY = new DefaultLogEventFactory();
93          }
94      }
95  
96      /**
97       * Default constructor.
98       */
99      public LoggerConfig() {
100         this.logEventFactory = LOG_EVENT_FACTORY;
101         this.level = Level.ERROR;
102         this.name = "";
103         this.properties = null;
104         this.config = null;
105     }
106 
107     /**
108      * Constructor that sets the name, level and additive values.
109      *
110      * @param name The Logger name.
111      * @param level The Level.
112      * @param additive true if the Logger is additive, false otherwise.
113      */
114     public LoggerConfig(final String name, final Level level,
115             final boolean additive) {
116         this.logEventFactory = LOG_EVENT_FACTORY;
117         this.name = name;
118         this.level = level;
119         this.additive = additive;
120         this.properties = null;
121         this.config = null;
122     }
123 
124     protected LoggerConfig(final String name,
125             final List<AppenderRef> appenders, final Filter filter,
126             final Level level, final boolean additive,
127             final Property[] properties, final Configuration config,
128             final boolean includeLocation) {
129         super(filter);
130         this.logEventFactory = LOG_EVENT_FACTORY;
131         this.name = name;
132         this.appenderRefs = appenders;
133         this.level = level;
134         this.additive = additive;
135         this.includeLocation = includeLocation;
136         this.config = config;
137         if (properties != null && properties.length > 0) {
138             this.properties = new HashMap<Property, Boolean>(properties.length);
139             for (final Property prop : properties) {
140                 final boolean interpolate = prop.getValue().contains("${");
141                 this.properties.put(prop, interpolate);
142             }
143         } else {
144             this.properties = null;
145         }
146     }
147 
148     @Override
149     public Filter getFilter() {
150         return super.getFilter();
151     }
152 
153     /**
154      * Returns the name of the LoggerConfig.
155      *
156      * @return the name of the LoggerConfig.
157      */
158     public String getName() {
159         return name;
160     }
161 
162     /**
163      * Sets the parent of this LoggerConfig.
164      *
165      * @param parent the parent LoggerConfig.
166      */
167     public void setParent(final LoggerConfig parent) {
168         this.parent = parent;
169     }
170 
171     /**
172      * Returns the parent of this LoggerConfig.
173      *
174      * @return the LoggerConfig that is the parent of this one.
175      */
176     public LoggerConfig getParent() {
177         return this.parent;
178     }
179 
180     /**
181      * Adds an Appender to the LoggerConfig.
182      *
183      * @param appender The Appender to add.
184      * @param level The Level to use.
185      * @param filter A Filter for the Appender reference.
186      */
187     public <T extends Serializable> void addAppender(final Appender<T> appender, final Level level,
188             final Filter filter) {
189         appenders.put(appender.getName(), new AppenderControl<T>(appender, level,
190                 filter));
191     }
192 
193     /**
194      * Removes the Appender with the specific name.
195      *
196      * @param name The name of the Appender.
197      */
198     public void removeAppender(final String name) {
199         final AppenderControl ctl = appenders.remove(name);
200         if (ctl != null) {
201             cleanupFilter(ctl);
202         }
203     }
204 
205     /**
206      * Returns all Appenders as a Map.
207      *
208      * @return a Map with the Appender name as the key and the Appender as the
209      *         value.
210      */
211     public Map<String, Appender<?>> getAppenders() {
212         final Map<String, Appender<?>> map = new HashMap<String, Appender<?>>();
213         for (final Map.Entry<String, AppenderControl<?>> entry : appenders
214                 .entrySet()) {
215             map.put(entry.getKey(), entry.getValue().getAppender());
216         }
217         return map;
218     }
219 
220     /**
221      * Removes all Appenders.
222      */
223     protected void clearAppenders() {
224         waitForCompletion();
225         final Collection<AppenderControl<?>> controls = appenders.values();
226         final Iterator<AppenderControl<?>> iterator = controls.iterator();
227         while (iterator.hasNext()) {
228             final AppenderControl<?> ctl = iterator.next();
229             iterator.remove();
230             cleanupFilter(ctl);
231         }
232     }
233 
234     private void cleanupFilter(final AppenderControl ctl) {
235         final Filter filter = ctl.getFilter();
236         if (filter != null) {
237             ctl.removeFilter(filter);
238             if (filter instanceof LifeCycle) {
239                 ((LifeCycle) filter).stop();
240             }
241         }
242     }
243 
244     /**
245      * Returns the Appender references.
246      *
247      * @return a List of all the Appender names attached to this LoggerConfig.
248      */
249     public List<AppenderRef> getAppenderRefs() {
250         return appenderRefs;
251     }
252 
253     /**
254      * Sets the logging Level.
255      *
256      * @param level The logging Level.
257      */
258     public void setLevel(final Level level) {
259         this.level = level;
260     }
261 
262     /**
263      * Returns the logging Level.
264      *
265      * @return the logging Level.
266      */
267     public Level getLevel() {
268         return level;
269     }
270 
271     /**
272      * Returns the LogEventFactory.
273      *
274      * @return the LogEventFactory.
275      */
276     public LogEventFactory getLogEventFactory() {
277         return logEventFactory;
278     }
279 
280     /**
281      * Sets the LogEventFactory. Usually the LogEventFactory will be this
282      * LoggerConfig.
283      *
284      * @param logEventFactory the LogEventFactory.
285      */
286     public void setLogEventFactory(final LogEventFactory logEventFactory) {
287         this.logEventFactory = logEventFactory;
288     }
289 
290     /**
291      * Returns the valid of the additive flag.
292      *
293      * @return true if the LoggerConfig is additive, false otherwise.
294      */
295     public boolean isAdditive() {
296         return additive;
297     }
298 
299     /**
300      * Sets the additive setting.
301      *
302      * @param additive true if the LoggerConfig should be additive, false
303      *            otherwise.
304      */
305     public void setAdditive(final boolean additive) {
306         this.additive = additive;
307     }
308 
309     /**
310      * Returns the value of logger configuration attribute {@code includeLocation},
311      * or, if no such attribute was configured, {@code true} if logging is
312      * synchronous or {@code false} if logging is asynchronous.
313      *
314      * @return whether location should be passed downstream
315      */
316     public boolean isIncludeLocation() {
317         return includeLocation;
318     }
319 
320     /**
321      * Returns an unmodifiable map with the configuration properties, or
322      * {@code null} if this {@code LoggerConfig} does not have any configuration
323      * properties.
324      * <p>
325      * For each {@code Property} key in the map, the value is {@code true} if
326      * the property value has a variable that needs to be substituted.
327      *
328      * @return an unmodifiable map with the configuration properties, or
329      *         {@code null}
330      * @see Configuration#getSubst()
331      * @see StrSubstitutor
332      */
333     // LOG4J2-157
334     public Map<Property, Boolean> getProperties() {
335         return properties == null ? null : Collections
336                 .unmodifiableMap(properties);
337     }
338 
339     /**
340      * Logs an event.
341      *
342      * @param loggerName The name of the Logger.
343      * @param marker A Marker or null if none is present.
344      * @param fqcn The fully qualified class name of the caller.
345      * @param level The event Level.
346      * @param data The Message.
347      * @param t A Throwable or null.
348      */
349     public void log(final String loggerName, final Marker marker,
350             final String fqcn, final Level level, final Message data,
351             final Throwable t) {
352         List<Property> props = null;
353         if (properties != null) {
354             props = new ArrayList<Property>(properties.size());
355 
356             for (final Map.Entry<Property, Boolean> entry : properties
357                     .entrySet()) {
358                 final Property prop = entry.getKey();
359                 final String value = entry.getValue() ? config.getSubst()
360                         .replace(prop.getValue()) : prop.getValue();
361                 props.add(Property.createProperty(prop.getName(), value));
362             }
363         }
364         final LogEvent event = logEventFactory.createEvent(loggerName, marker,
365                 fqcn, level, data, props, t);
366         log(event);
367     }
368 
369     /**
370      * Waits for all log events to complete before shutting down this
371      * loggerConfig.
372      */
373     private synchronized void waitForCompletion() {
374         if (shutdown) {
375             return;
376         }
377         shutdown = true;
378         int retries = 0;
379         while (counter.get() > 0) {
380             try {
381                 wait(WAIT_TIME * (retries + 1));
382             } catch (final InterruptedException ie) {
383                 if (++retries > MAX_RETRIES) {
384                     break;
385                 }
386             }
387         }
388     }
389 
390     /**
391      * Logs an event.
392      *
393      * @param event The log event.
394      */
395     public void log(final LogEvent event) {
396 
397         counter.incrementAndGet();
398         try {
399             if (isFiltered(event)) {
400                 return;
401             }
402 
403             event.setIncludeLocation(isIncludeLocation());
404 
405             callAppenders(event);
406 
407             if (additive && parent != null) {
408                 parent.log(event);
409             }
410         } finally {
411             if (counter.decrementAndGet() == 0) {
412                 synchronized (this) {
413                     if (shutdown) {
414                         notifyAll();
415                     }
416                 }
417 
418             }
419         }
420     }
421 
422     protected void callAppenders(final LogEvent event) {
423         for (final AppenderControl control : appenders.values()) {
424             control.callAppender(event);
425         }
426     }
427 
428 
429     @Override
430     public String toString() {
431         return name == null || name.length() == 0 ? "root" : name;
432     }
433 
434     /**
435      * Factory method to create a LoggerConfig.
436      *
437      * @param additivity True if additive, false otherwise.
438      * @param levelName The Level to be associated with the Logger.
439      * @param loggerName The name of the Logger.
440      * @param includeLocation whether location should be passed downstream
441      * @param refs An array of Appender names.
442      * @param properties Properties to pass to the Logger.
443      * @param config The Configuration.
444      * @param filter A Filter.
445      * @return A new LoggerConfig.
446      */
447     @PluginFactory
448     public static LoggerConfig createLogger(
449             @PluginAttr("additivity") final String additivity,
450             @PluginAttr("level") final String levelName,
451             @PluginAttr("name") final String loggerName,
452             @PluginAttr("includeLocation") final String includeLocation,
453             @PluginElement("appender-ref") final AppenderRef[] refs,
454             @PluginElement("properties") final Property[] properties,
455             @PluginConfiguration final Configuration config,
456             @PluginElement("filters") final Filter filter) {
457         if (loggerName == null) {
458             LOGGER.error("Loggers cannot be configured without a name");
459             return null;
460         }
461 
462         final List<AppenderRef> appenderRefs = Arrays.asList(refs);
463         Level level;
464         try {
465             level = Level.toLevel(levelName, Level.ERROR);
466         } catch (final Exception ex) {
467             LOGGER.error(
468                     "Invalid Log level specified: {}. Defaulting to Error",
469                     levelName);
470             level = Level.ERROR;
471         }
472         final String name = loggerName.equals("root") ? "" : loggerName;
473         final boolean additive = additivity == null ? true : Boolean
474                 .parseBoolean(additivity);
475 
476         return new LoggerConfig(name, appenderRefs, filter, level, additive,
477                 properties, config, includeLocation(includeLocation));
478     }
479 
480     // Note: for asynchronous loggers, includeLocation default is FALSE,
481     // for synchronous loggers, includeLocation default is TRUE.
482     protected static boolean includeLocation(String includeLocationConfigValue) {
483         if (includeLocationConfigValue == null) {
484             final boolean sync = !AsyncLoggerContextSelector.class.getName()
485                     .equals(System.getProperty(Constants.LOG4J_CONTEXT_SELECTOR));
486             return sync;
487         }
488         return Boolean.parseBoolean(includeLocationConfigValue);
489     }
490 
491     /**
492      * The root Logger.
493      */
494     @Plugin(name = "root", category = "Core", printObject = true)
495     public static class RootLogger extends LoggerConfig {
496 
497         @PluginFactory
498         public static LoggerConfig createLogger(
499                 @PluginAttr("additivity") final String additivity,
500                 @PluginAttr("level") final String levelName,
501                 @PluginAttr("includeLocation") final String includeLocation,
502                 @PluginElement("appender-ref") final AppenderRef[] refs,
503                 @PluginElement("properties") final Property[] properties,
504                 @PluginConfiguration final Configuration config,
505                 @PluginElement("filters") final Filter filter) {
506             final List<AppenderRef> appenderRefs = Arrays.asList(refs);
507             Level level;
508             try {
509                 level = Level.toLevel(levelName, Level.ERROR);
510             } catch (final Exception ex) {
511                 LOGGER.error(
512                         "Invalid Log level specified: {}. Defaulting to Error",
513                         levelName);
514                 level = Level.ERROR;
515             }
516             final boolean additive = additivity == null ? true : Boolean
517                     .parseBoolean(additivity);
518 
519             return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs,
520                     filter, level, additive, properties, config,
521                     includeLocation(includeLocation));
522         }
523     }
524 
525 }