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