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