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