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