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