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.message.Message;
48  import org.apache.logging.log4j.util.LoaderUtil;
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 Map<Property, Boolean> propertiesMap;
71      private final List<Property> properties;
72      private final boolean propertiesRequireLookup;
73      private final Configuration config;
74      private final ReliabilityStrategy reliabilityStrategy;
75  
76      static {
77          final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY);
78          if (factory != null) {
79              try {
80                  final Class<?> clazz = LoaderUtil.loadClass(factory);
81                  if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) {
82                      LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance();
83                  }
84              } catch (final Exception ex) {
85                  LOGGER.error("Unable to create LogEventFactory {}", factory, ex);
86              }
87          }
88          if (LOG_EVENT_FACTORY == null) {
89              LOG_EVENT_FACTORY = Constants.ENABLE_THREADLOCALS
90                      ? new ReusableLogEventFactory()
91                      : new DefaultLogEventFactory();
92          }
93      }
94  
95      /**
96       * Default constructor.
97       */
98      public LoggerConfig() {
99          this.logEventFactory = LOG_EVENT_FACTORY;
100         this.level = Level.ERROR;
101         this.name = Strings.EMPTY;
102         this.properties = null;
103         this.propertiesRequireLookup = false;
104         this.config = null;
105         this.reliabilityStrategy = new DefaultReliabilityStrategy(this);
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, final boolean additive) {
116         this.logEventFactory = LOG_EVENT_FACTORY;
117         this.name = name;
118         this.level = level;
119         this.additive = additive;
120         this.properties = null;
121         this.propertiesRequireLookup = false;
122         this.config = null;
123         this.reliabilityStrategy = new DefaultReliabilityStrategy(this);
124     }
125 
126     protected LoggerConfig(final String name, final List<AppenderRef> appenders, final Filter filter,
127             final Level level, final boolean additive, final Property[] properties, final Configuration config,
128             final boolean includeLocation) {
129         super(filter);
130         this.logEventFactory = LOG_EVENT_FACTORY;
131         this.name = name;
132         this.appenderRefs = appenders;
133         this.level = level;
134         this.additive = additive;
135         this.includeLocation = includeLocation;
136         this.config = config;
137         if (properties != null && properties.length > 0) {
138             this.properties = Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(
139                     properties, properties.length)));
140         } else {
141             this.properties = null;
142         }
143         this.propertiesRequireLookup = containsPropertyRequiringLookup(properties);
144         this.reliabilityStrategy = config.getReliabilityStrategy(this);
145     }
146 
147     private static boolean containsPropertyRequiringLookup(final Property[] properties) {
148         if (properties == null) {
149             return false;
150         }
151         for (int i = 0; i < properties.length; i++) {
152             if (properties[i].isValueNeedsLookup()) {
153                 return true;
154             }
155         }
156         return false;
157     }
158 
159     @Override
160     public Filter getFilter() {
161         return super.getFilter();
162     }
163 
164     /**
165      * Returns the name of the LoggerConfig.
166      *
167      * @return the name of the LoggerConfig.
168      */
169     public String getName() {
170         return name;
171     }
172 
173     /**
174      * Sets the parent of this LoggerConfig.
175      *
176      * @param parent the parent LoggerConfig.
177      */
178     public void setParent(final LoggerConfig parent) {
179         this.parent = parent;
180     }
181 
182     /**
183      * Returns the parent of this LoggerConfig.
184      *
185      * @return the LoggerConfig that is the parent of this one.
186      */
187     public LoggerConfig getParent() {
188         return this.parent;
189     }
190 
191     /**
192      * Adds an Appender to the LoggerConfig.
193      *
194      * @param appender The Appender to add.
195      * @param level The Level to use.
196      * @param filter A Filter for the Appender reference.
197      */
198     public void addAppender(final Appender appender, final Level level, final Filter filter) {
199         appenders.add(new AppenderControl(appender, level, filter));
200     }
201 
202     /**
203      * Removes the Appender with the specific name.
204      *
205      * @param name The name of the Appender.
206      */
207     public void removeAppender(final String name) {
208         AppenderControl removed = null;
209         while ((removed = appenders.remove(name)) != null) {
210             cleanupFilter(removed);
211         }
212     }
213 
214     /**
215      * Returns all Appenders as a Map.
216      *
217      * @return a Map with the Appender name as the key and the Appender as the value.
218      */
219     public Map<String, Appender> getAppenders() {
220         return appenders.asMap();
221     }
222 
223     /**
224      * Removes all Appenders.
225      */
226     protected void clearAppenders() {
227         do {
228             final AppenderControl[] original = appenders.clear();
229             for (final AppenderControl ctl : original) {
230                 cleanupFilter(ctl);
231             }
232         } while (!appenders.isEmpty());
233     }
234 
235     private void cleanupFilter(final AppenderControl ctl) {
236         final Filter filter = ctl.getFilter();
237         if (filter != null) {
238             ctl.removeFilter(filter);
239             filter.stop();
240         }
241     }
242 
243     /**
244      * Returns the Appender references.
245      *
246      * @return a List of all the Appender names attached to this LoggerConfig.
247      */
248     public List<AppenderRef> getAppenderRefs() {
249         return appenderRefs;
250     }
251 
252     /**
253      * Sets the logging Level.
254      *
255      * @param level The logging Level.
256      */
257     public void setLevel(final Level level) {
258         this.level = level;
259     }
260 
261     /**
262      * Returns the logging Level.
263      *
264      * @return the logging Level.
265      */
266     public Level getLevel() {
267         return level == null ? parent.getLevel() : level;
268     }
269 
270     /**
271      * Returns the LogEventFactory.
272      *
273      * @return the LogEventFactory.
274      */
275     public LogEventFactory getLogEventFactory() {
276         return logEventFactory;
277     }
278 
279     /**
280      * Sets the LogEventFactory. Usually the LogEventFactory will be this LoggerConfig.
281      *
282      * @param logEventFactory the LogEventFactory.
283      */
284     public void setLogEventFactory(final LogEventFactory logEventFactory) {
285         this.logEventFactory = logEventFactory;
286     }
287 
288     /**
289      * Returns the valid of the additive flag.
290      *
291      * @return true if the LoggerConfig is additive, false otherwise.
292      */
293     public boolean isAdditive() {
294         return additive;
295     }
296 
297     /**
298      * Sets the additive setting.
299      *
300      * @param additive true if the LoggerConfig should be additive, false otherwise.
301      */
302     public void setAdditive(final boolean additive) {
303         this.additive = additive;
304     }
305 
306     /**
307      * Returns the value of logger configuration attribute {@code includeLocation}, or, if no such attribute was
308      * configured, {@code true} if logging is synchronous or {@code false} if logging is asynchronous.
309      *
310      * @return whether location should be passed downstream
311      */
312     public boolean isIncludeLocation() {
313         return includeLocation;
314     }
315 
316     /**
317      * Returns an unmodifiable map with the configuration properties, or {@code null} if this {@code LoggerConfig} does
318      * not have any configuration properties.
319      * <p>
320      * For each {@code Property} key in the map, the value is {@code true} if the property value has a variable that
321      * needs to be substituted.
322      *
323      * @return an unmodifiable map with the configuration properties, or {@code null}
324      * @see Configuration#getStrSubstitutor()
325      * @see StrSubstitutor
326      * @deprecated use {@link #getPropertyList()} instead
327      */
328     // LOG4J2-157
329     @Deprecated
330     public Map<Property, Boolean> getProperties() {
331         if (properties == null) {
332             return null;
333         }
334         if (propertiesMap == null) { // lazily initialize: only used by user custom code, not by Log4j any more
335             final Map<Property, Boolean> result = new HashMap<>(properties.size() * 2);
336             for (int i = 0; i < properties.size(); i++) {
337                 result.put(properties.get(i), Boolean.valueOf(properties.get(i).isValueNeedsLookup()));
338             }
339             propertiesMap = Collections.unmodifiableMap(result);
340         }
341         return propertiesMap;
342     }
343 
344     /**
345      * Returns an unmodifiable list with the configuration properties, or {@code null} if this {@code LoggerConfig} does
346      * not have any configuration properties.
347      * <p>
348      * Each {@code Property} in the list has an attribute {@link Property#isValueNeedsLookup() valueNeedsLookup} that
349      * is {@code true} if the property value has a variable that needs to be substituted.
350      *
351      * @return an unmodifiable list with the configuration properties, or {@code null}
352      * @see Configuration#getStrSubstitutor()
353      * @see StrSubstitutor
354      * @since 2.7
355      */
356     public List<Property> getPropertyList() {
357         return properties;
358     }
359 
360     public boolean isPropertiesRequireLookup() {
361         return propertiesRequireLookup;
362     }
363 
364     /**
365      * Logs an event.
366      *
367      * @param loggerName The name of the Logger.
368      * @param fqcn The fully qualified class name of the caller.
369      * @param marker A Marker or null if none is present.
370      * @param level The event Level.
371      * @param data The Message.
372      * @param t A Throwable or null.
373      */
374     @PerformanceSensitive("allocation")
375     public void log(final String loggerName, final String fqcn, final Marker marker, final Level level,
376             final Message data, final Throwable t) {
377         List<Property> props = null;
378         if (!propertiesRequireLookup) {
379             props = properties;
380         } else {
381             if (properties != null) {
382                 props = new ArrayList<>(properties.size());
383                 final LogEvent event = Log4jLogEvent.newBuilder()
384                         .setMessage(data)
385                         .setMarker(marker)
386                         .setLevel(level)
387                         .setLoggerName(loggerName)
388                         .setLoggerFqcn(fqcn)
389                         .setThrown(t)
390                         .build();
391                 for (int i = 0; i < properties.size(); i++) {
392                     final Property prop = properties.get(i);
393                     final String value = prop.isValueNeedsLookup() // since LOG4J2-1575
394                             ? config.getStrSubstitutor().replace(event, prop.getValue()) //
395                             : prop.getValue();
396                     props.add(Property.createProperty(prop.getName(), value));
397                 }
398             }
399         }
400         final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
401         try {
402             log(logEvent);
403         } finally {
404             // LOG4J2-1583 prevent scrambled logs when logging calls are nested (logging in toString())
405             ReusableLogEventFactory.release(logEvent);
406         }
407     }
408 
409     /**
410      * Logs an event.
411      *
412      * @param event The log event.
413      */
414     public void log(final LogEvent event) {
415         if (!isFiltered(event)) {
416             processLogEvent(event);
417         }
418     }
419 
420     /**
421      * Returns the object responsible for ensuring log events are delivered to a working appender, even during or after
422      * a reconfiguration.
423      *
424      * @return the object responsible for delivery of log events to the appender
425      */
426     public ReliabilityStrategy getReliabilityStrategy() {
427         return reliabilityStrategy;
428     }
429 
430     private void processLogEvent(final LogEvent event) {
431         event.setIncludeLocation(isIncludeLocation());
432         callAppenders(event);
433         logParent(event);
434     }
435 
436     private void logParent(final LogEvent event) {
437         if (additive && parent != null) {
438             parent.log(event);
439         }
440     }
441 
442     @PerformanceSensitive("allocation")
443     protected void callAppenders(final LogEvent event) {
444         final AppenderControl[] controls = appenders.get();
445         //noinspection ForLoopReplaceableByForEach
446         for (int i = 0; i < controls.length; i++) {
447             controls[i].callAppender(event);
448         }
449     }
450 
451     @Override
452     public String toString() {
453         return Strings.isEmpty(name) ? ROOT : name;
454     }
455 
456     /**
457      * Factory method to create a LoggerConfig.
458      *
459      * @param additivity True if additive, false otherwise.
460      * @param level The Level to be associated with the Logger.
461      * @param loggerName The name of the Logger.
462      * @param includeLocation whether location should be passed downstream
463      * @param refs An array of Appender names.
464      * @param properties Properties to pass to the Logger.
465      * @param config The Configuration.
466      * @param filter A Filter.
467      * @return A new LoggerConfig.
468      * @deprecated Deprecated in 2.7; use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)}
469      */
470     @Deprecated
471     public static LoggerConfig createLogger(final String additivity,
472             // @formatter:off
473             final Level level,
474             @PluginAttribute("name") final String loggerName,
475             final String includeLocation,
476             final AppenderRef[] refs,
477             final Property[] properties,
478             @PluginConfiguration final Configuration config,
479             final Filter filter) {
480             // @formatter:on
481         if (loggerName == null) {
482             LOGGER.error("Loggers cannot be configured without a name");
483             return null;
484         }
485 
486         final List<AppenderRef> appenderRefs = Arrays.asList(refs);
487         final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName;
488         final boolean additive = Booleans.parseBoolean(additivity, true);
489 
490         return new LoggerConfig(name, appenderRefs, filter, level, additive, properties, config,
491                 includeLocation(includeLocation));
492     }
493 
494     /**
495      * Factory method to create a LoggerConfig.
496      *
497      * @param additivity true if additive, false otherwise.
498      * @param level The Level to be associated with the Logger.
499      * @param loggerName The name of the Logger.
500      * @param includeLocation whether location should be passed downstream
501      * @param refs An array of Appender names.
502      * @param properties Properties to pass to the Logger.
503      * @param config The Configuration.
504      * @param filter A Filter.
505      * @return A new LoggerConfig.
506      * @since 2.6
507      */
508     @PluginFactory
509     public static LoggerConfig createLogger(
510          // @formatter:off
511         @PluginAttribute(value = "additivity", defaultBoolean = true) final boolean additivity,
512         @PluginAttribute("level") final Level level,
513         @Required(message = "Loggers cannot be configured without a name") @PluginAttribute("name") final String loggerName,
514         @PluginAttribute("includeLocation") final String includeLocation,
515         @PluginElement("AppenderRef") final AppenderRef[] refs,
516         @PluginElement("Properties") final Property[] properties,
517         @PluginConfiguration final Configuration config,
518         @PluginElement("Filter") final Filter filter
519         // @formatter:on
520     ) {
521         final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName;
522         return new LoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config,
523             includeLocation(includeLocation));
524     }
525 
526     // Note: for asynchronous loggers, includeLocation default is FALSE,
527     // for synchronous loggers, includeLocation default is TRUE.
528     protected static boolean includeLocation(final String includeLocationConfigValue) {
529         if (includeLocationConfigValue == null) {
530             final boolean sync = !AsyncLoggerContextSelector.isSelected();
531             return sync;
532         }
533         return Boolean.parseBoolean(includeLocationConfigValue);
534     }
535 
536     /**
537      * The root Logger.
538      */
539     @Plugin(name = ROOT, category = "Core", printObject = true)
540     public static class RootLogger extends LoggerConfig {
541 
542         @PluginFactory
543         public static LoggerConfig createLogger(
544                 // @formatter:off
545                 @PluginAttribute("additivity") final String additivity,
546                 @PluginAttribute("level") final Level level,
547                 @PluginAttribute("includeLocation") final String includeLocation,
548                 @PluginElement("AppenderRef") final AppenderRef[] refs,
549                 @PluginElement("Properties") final Property[] properties,
550                 @PluginConfiguration final Configuration config,
551                 @PluginElement("Filter") final Filter filter) {
552                 // @formatter:on
553             final List<AppenderRef> appenderRefs = Arrays.asList(refs);
554             final Level actualLevel = level == null ? Level.ERROR : level;
555             final boolean additive = Booleans.parseBoolean(additivity, true);
556 
557             return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs, filter, actualLevel, additive,
558                     properties, config, includeLocation(includeLocation));
559         }
560     }
561 
562 }