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