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