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 java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.TimeUnit;
29  import java.util.concurrent.atomic.AtomicBoolean;
30  import java.util.concurrent.atomic.AtomicInteger;
31  import java.util.concurrent.locks.Condition;
32  import java.util.concurrent.locks.Lock;
33  import java.util.concurrent.locks.ReentrantLock;
34  
35  import org.apache.logging.log4j.Level;
36  import org.apache.logging.log4j.LogManager;
37  import org.apache.logging.log4j.Marker;
38  import org.apache.logging.log4j.core.Appender;
39  import org.apache.logging.log4j.core.Filter;
40  import org.apache.logging.log4j.core.LogEvent;
41  import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
42  import org.apache.logging.log4j.core.config.plugins.Plugin;
43  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
44  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
45  import org.apache.logging.log4j.core.config.plugins.PluginElement;
46  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
47  import org.apache.logging.log4j.core.filter.AbstractFilterable;
48  import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
49  import org.apache.logging.log4j.core.impl.LogEventFactory;
50  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
51  import org.apache.logging.log4j.core.util.Booleans;
52  import org.apache.logging.log4j.core.util.Constants;
53  import org.apache.logging.log4j.core.util.Loader;
54  import org.apache.logging.log4j.message.Message;
55  import org.apache.logging.log4j.util.PropertiesUtil;
56  import org.apache.logging.log4j.util.Strings;
57  
58  /**
59   * Logger object that is created via configuration.
60   */
61  @Plugin(name = "logger", category = "Core", printObject = true)
62  public class LoggerConfig extends AbstractFilterable {
63  
64      private static final int MAX_RETRIES = 3;
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 final AtomicBoolean shutdown = new AtomicBoolean(false);
77      private final Map<Property, Boolean> properties;
78      private final Configuration config;
79      private final Lock shutdownLock = new ReentrantLock();
80      private final Condition noLogEvents = shutdownLock.newCondition(); // should only be used when shutdown == true
81  
82      static {
83          final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY);
84          if (factory != null) {
85              try {
86                  final Class<?> clazz = Loader.loadClass(factory);
87                  if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) {
88                      LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance();
89                  }
90              } catch (final Exception ex) {
91                  LOGGER.error("Unable to create LogEventFactory {}", factory, ex);
92              }
93          }
94          if (LOG_EVENT_FACTORY == null) {
95              LOG_EVENT_FACTORY = new DefaultLogEventFactory();
96          }
97      }
98  
99      /**
100      * Default constructor.
101      */
102     public LoggerConfig() {
103         this.logEventFactory = LOG_EVENT_FACTORY;
104         this.level = Level.ERROR;
105         this.name = Strings.EMPTY;
106         this.properties = null;
107         this.config = null;
108     }
109 
110     /**
111      * Constructor that sets the name, level and additive values.
112      *
113      * @param name The Logger name.
114      * @param level The Level.
115      * @param additive true if the Logger is additive, false otherwise.
116      */
117     public LoggerConfig(final String name, final Level level,
118             final boolean additive) {
119         this.logEventFactory = LOG_EVENT_FACTORY;
120         this.name = name;
121         this.level = level;
122         this.additive = additive;
123         this.properties = null;
124         this.config = null;
125     }
126 
127     protected LoggerConfig(final String name,
128             final List<AppenderRef> appenders, final Filter filter,
129             final Level level, final boolean additive,
130             final Property[] properties, final Configuration config,
131             final boolean includeLocation) {
132         super(filter);
133         this.logEventFactory = LOG_EVENT_FACTORY;
134         this.name = name;
135         this.appenderRefs = appenders;
136         this.level = level;
137         this.additive = additive;
138         this.includeLocation = includeLocation;
139         this.config = config;
140         if (properties != null && properties.length > 0) {
141             this.properties = new HashMap<Property, Boolean>(properties.length);
142             for (final Property prop : properties) {
143                 final boolean interpolate = prop.getValue().contains("${");
144                 this.properties.put(prop, interpolate);
145             }
146         } else {
147             this.properties = null;
148         }
149     }
150 
151     @Override
152     public Filter getFilter() {
153         return super.getFilter();
154     }
155 
156     /**
157      * Returns the name of the LoggerConfig.
158      *
159      * @return the name of the LoggerConfig.
160      */
161     public String getName() {
162         return name;
163     }
164 
165     /**
166      * Sets the parent of this LoggerConfig.
167      *
168      * @param parent the parent LoggerConfig.
169      */
170     public void setParent(final LoggerConfig parent) {
171         this.parent = parent;
172     }
173 
174     /**
175      * Returns the parent of this LoggerConfig.
176      *
177      * @return the LoggerConfig that is the parent of this one.
178      */
179     public LoggerConfig getParent() {
180         return this.parent;
181     }
182 
183     /**
184      * Adds an Appender to the LoggerConfig.
185      *
186      * @param appender The Appender to add.
187      * @param level The Level to use.
188      * @param filter A Filter for the Appender reference.
189      */
190     public void addAppender(final Appender appender, final Level level,
191             final Filter filter) {
192         appenders.put(appender.getName(), new AppenderControl(appender, level,
193                 filter));
194     }
195 
196     /**
197      * Removes the Appender with the specific name.
198      *
199      * @param name The name of the Appender.
200      */
201     public void removeAppender(final String name) {
202         final AppenderControl ctl = appenders.remove(name);
203         if (ctl != null) {
204             cleanupFilter(ctl);
205         }
206     }
207 
208     /**
209      * Returns all Appenders as a Map.
210      *
211      * @return a Map with the Appender name as the key and the Appender as the
212      *         value.
213      */
214     public Map<String, Appender> getAppenders() {
215         final Map<String, Appender> map = new HashMap<String, Appender>();
216         for (final Map.Entry<String, AppenderControl> entry : appenders
217                 .entrySet()) {
218             map.put(entry.getKey(), entry.getValue().getAppender());
219         }
220         return map;
221     }
222 
223     /**
224      * Removes all Appenders.
225      */
226     protected void clearAppenders() {
227         waitForCompletion();
228         final Collection<AppenderControl> controls = appenders.values();
229         final Iterator<AppenderControl> iterator = controls.iterator();
230         while (iterator.hasNext()) {
231             final AppenderControl ctl = iterator.next();
232             iterator.remove();
233             cleanupFilter(ctl);
234         }
235     }
236 
237     private void cleanupFilter(final AppenderControl ctl) {
238         final Filter filter = ctl.getFilter();
239         if (filter != null) {
240             ctl.removeFilter(filter);
241             filter.stop();
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 == null ? parent.getLevel() : 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 fqcn The fully qualified class name of the caller.
345      * @param marker A Marker or null if none is present.
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 String fqcn,
351             final Marker marker, 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.entrySet()) {
358                 final Property prop = entry.getKey();
359                 final String value = entry.getValue() ? config.getStrSubstitutor()
360                         .replace(prop.getValue()) : prop.getValue();
361                 props.add(Property.createProperty(prop.getName(), value));
362             }
363         }
364         final LogEvent event = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
365         log(event);
366     }
367 
368     /**
369      * Waits for all log events to complete before shutting down this
370      * loggerConfig.
371      */
372     private void waitForCompletion() {
373         shutdownLock.lock();
374         try {
375             if (shutdown.compareAndSet(false, true)) {
376                 int retries = 0;
377                 while (counter.get() > 0) {
378                     try {
379                         noLogEvents.await(retries + 1, TimeUnit.SECONDS);
380                     } catch (final InterruptedException ie) {
381                         if (++retries > MAX_RETRIES) {
382                             break;
383                         }
384                     }
385                 }
386             }
387         } finally {
388             shutdownLock.unlock();
389         }
390     }
391 
392     /**
393      * Logs an event.
394      *
395      * @param event The log event.
396      */
397     public void log(final LogEvent event) {
398 
399         counter.incrementAndGet();
400         try {
401             if (isFiltered(event)) {
402                 return;
403             }
404 
405             event.setIncludeLocation(isIncludeLocation());
406 
407             callAppenders(event);
408 
409             if (additive && parent != null) {
410                 parent.log(event);
411             }
412         } finally {
413             if (counter.decrementAndGet() == 0) {
414                 shutdownLock.lock();
415                 try {
416                     if (shutdown.get()) {
417                         noLogEvents.signalAll();
418                     }
419                 } finally {
420                     shutdownLock.unlock();
421                 }
422             }
423         }
424     }
425 
426     protected void callAppenders(final LogEvent event) {
427         for (final AppenderControl control : appenders.values()) {
428             control.callAppender(event);
429         }
430     }
431 
432 
433     @Override
434     public String toString() {
435         return Strings.isEmpty(name) ? "root" : name;
436     }
437 
438     /**
439      * Factory method to create a LoggerConfig.
440      *
441      * @param additivity True if additive, false otherwise.
442      * @param level The Level to be associated with the Logger.
443      * @param loggerName The name of the Logger.
444      * @param includeLocation whether location should be passed downstream
445      * @param refs An array of Appender names.
446      * @param properties Properties to pass to the Logger.
447      * @param config The Configuration.
448      * @param filter A Filter.
449      * @return A new LoggerConfig.
450      */
451     @PluginFactory
452     public static LoggerConfig createLogger(
453             @PluginAttribute("additivity") final String additivity,
454             @PluginAttribute("level") final Level level,
455             @PluginAttribute("name") final String loggerName,
456             @PluginAttribute("includeLocation") final String includeLocation,
457             @PluginElement("AppenderRef") final AppenderRef[] refs,
458             @PluginElement("Properties") final Property[] properties,
459             @PluginConfiguration final Configuration config,
460             @PluginElement("Filter") final Filter filter) {
461         if (loggerName == null) {
462             LOGGER.error("Loggers cannot be configured without a name");
463             return null;
464         }
465 
466         final List<AppenderRef> appenderRefs = Arrays.asList(refs);
467         final String name = loggerName.equals("root") ? Strings.EMPTY : loggerName;
468         final boolean additive = Booleans.parseBoolean(additivity, true);
469 
470         return new LoggerConfig(name, appenderRefs, filter, level, additive,
471                 properties, config, includeLocation(includeLocation));
472     }
473 
474     // Note: for asynchronous loggers, includeLocation default is FALSE,
475     // for synchronous loggers, includeLocation default is TRUE.
476     protected static boolean includeLocation(final String includeLocationConfigValue) {
477         if (includeLocationConfigValue == null) {
478             final boolean sync = !AsyncLoggerContextSelector.class.getName()
479                     .equals(System.getProperty(Constants.LOG4J_CONTEXT_SELECTOR));
480             return sync;
481         }
482         return Boolean.parseBoolean(includeLocationConfigValue);
483     }
484 
485     /**
486      * The root Logger.
487      */
488     @Plugin(name = "root", category = "Core", printObject = true)
489     public static class RootLogger extends LoggerConfig {
490 
491         @PluginFactory
492         public static LoggerConfig createLogger(
493                 @PluginAttribute("additivity") final String additivity,
494                 @PluginAttribute("level") final Level level,
495                 @PluginAttribute("includeLocation") final String includeLocation,
496                 @PluginElement("AppenderRef") final AppenderRef[] refs,
497                 @PluginElement("Properties") final Property[] properties,
498                 @PluginConfiguration final Configuration config,
499                 @PluginElement("Filter") final Filter filter) {
500             final List<AppenderRef> appenderRefs = Arrays.asList(refs);
501             final Level actualLevel = level == null ? Level.ERROR : level;
502             final boolean additive = Booleans.parseBoolean(additivity, true);
503 
504             return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs,
505                     filter, actualLevel, additive, properties, config,
506                     includeLocation(includeLocation));
507         }
508     }
509 
510 }