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.Collection;
    22  import java.util.Collections;
    23  import java.util.HashMap;
    24  import java.util.Iterator;
    25  import java.util.List;
    26  import java.util.Map;
    27  import java.util.concurrent.ConcurrentHashMap;
    28  import java.util.concurrent.TimeUnit;
    29  import java.util.concurrent.atomic.AtomicBoolean;
    30  import java.util.concurrent.atomic.AtomicInteger;
    31  import java.util.concurrent.locks.Condition;
    32  import java.util.concurrent.locks.Lock;
    33  import java.util.concurrent.locks.ReentrantLock;
    34
    35  import org.apache.logging.log4j.Level;
    36  import org.apache.logging.log4j.LogManager;
    37  import org.apache.logging.log4j.Marker;
    38  import org.apache.logging.log4j.core.Appender;
    39  import org.apache.logging.log4j.core.Filter;
    40  import org.apache.logging.log4j.core.LogEvent;
    41  import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
    42  import org.apache.logging.log4j.core.config.plugins.Plugin;
    43  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
    44  import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
    45  import org.apache.logging.log4j.core.config.plugins.PluginElement;
    46  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
    47  import org.apache.logging.log4j.core.filter.AbstractFilterable;
    48  import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
    49  import org.apache.logging.log4j.core.impl.LogEventFactory;
    50  import org.apache.logging.log4j.core.lookup.StrSubstitutor;
    51  import org.apache.logging.log4j.core.util.Booleans;
    52  import org.apache.logging.log4j.core.util.Constants;
    53  import org.apache.logging.log4j.core.util.Loader;
    54  import org.apache.logging.log4j.message.Message;
    55  import org.apache.logging.log4j.util.PropertiesUtil;
    56  import org.apache.logging.log4j.util.Strings;
    57
    58  /**
    59   * Logger object that is created via configuration.
    60   */
    61  @Plugin(name = "logger", category = Node.CATEGORY, printObject = true)
    62  public class LoggerConfig extends AbstractFilterable {
    63
    64      private static final long serialVersionUID = 1L;
    65
    66      private static final int MAX_RETRIES = 3;
    67      private static LogEventFactory LOG_EVENT_FACTORY = null;
    68
    69      private List<AppenderRef> appenderRefs = new ArrayList<AppenderRef>();
    70      private final Map<String, AppenderControl> appenders = new ConcurrentHashMap<String, AppenderControl>();
    71      private final String name;
    72      private LogEventFactory logEventFactory;
    73      private Level level;
    74      private boolean additive = true;
    75      private boolean includeLocation = true;
    76      private LoggerConfig parent;
    77      private final AtomicInteger counter = new AtomicInteger();
    78      private final AtomicBoolean shutdown = new AtomicBoolean(false);
    79      private final Map<Property, Boolean> properties;
    80      private final Configuration config;
    81      private final Lock shutdownLock = new ReentrantLock();
    82      private final Condition noLogEvents = shutdownLock.newCondition(); // should only be used when shutdown == true
    83
    84      static {
    85          final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY);
    86          if (factory != null) {
    87              try {
    88                  final Class<?> clazz = Loader.loadClass(factory);
    89                  if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) {
    90                      LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance();
    91                  }
    92              } catch (final Exception ex) {
    93                  LOGGER.error("Unable to create LogEventFactory {}", factory, ex);
    94              }
    95          }
    96          if (LOG_EVENT_FACTORY == null) {
    97              LOG_EVENT_FACTORY = new DefaultLogEventFactory();
    98          }
    99      }
    100
    101     /**
    102      * Default constructor.
    103      */
    104     public LoggerConfig() {
    105         this.logEventFactory = LOG_EVENT_FACTORY;
    106         this.level = Level.ERROR;
    107         this.name = Strings.EMPTY;
    108         this.properties = null;
    109         this.config = null;
    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,
    120             final boolean additive) {
    121         this.logEventFactory = LOG_EVENT_FACTORY;
    122         this.name = name;
    123         this.level = level;
    124         this.additive = additive;
    125         this.properties = null;
    126         this.config = null;
    127     }
    128
    129     protected LoggerConfig(final String name,
    130             final List<AppenderRef> appenders, final Filter filter,
    131             final Level level, final boolean additive,
    132             final Property[] properties, final Configuration config,
    133             final boolean includeLocation) {
    134         super(filter);
    135         this.logEventFactory = LOG_EVENT_FACTORY;
    136         this.name = name;
    137         this.appenderRefs = appenders;
    138         this.level = level;
    139         this.additive = additive;
    140         this.includeLocation = includeLocation;
    141         this.config = config;
    142         if (properties != null && properties.length > 0) {
    143             this.properties = new HashMap<Property, Boolean>(properties.length);
    144             for (final Property prop : properties) {
    145                 final boolean interpolate = prop.getValue().contains("${");
    146                 this.properties.put(prop, interpolate);
    147             }
    148         } else {
    149             this.properties = null;
    150         }
    151     }
    152
    153     @Override
    154     public Filter getFilter() {
    155         return super.getFilter();
    156     }
    157
    158     /**
    159      * Returns the name of the LoggerConfig.
    160      *
    161      * @return the name of the LoggerConfig.
    162      */
    163     public String getName() {
    164         return name;
    165     }
    166
    167     /**
    168      * Sets the parent of this LoggerConfig.
    169      *
    170      * @param parent the parent LoggerConfig.
    171      */
    172     public void setParent(final LoggerConfig parent) {
    173         this.parent = parent;
    174     }
    175
    176     /**
    177      * Returns the parent of this LoggerConfig.
    178      *
    179      * @return the LoggerConfig that is the parent of this one.
    180      */
    181     public LoggerConfig getParent() {
    182         return this.parent;
    183     }
    184
    185     /**
    186      * Adds an Appender to the LoggerConfig.
    187      *
    188      * @param appender The Appender to add.
    189      * @param level The Level to use.
    190      * @param filter A Filter for the Appender reference.
    191      */
    192     public void addAppender(final Appender appender, final Level level,
    193             final Filter filter) {
    194         appenders.put(appender.getName(), new AppenderControl(appender, level,
    195                 filter));
    196     }
    197
    198     /**
    199      * Removes the Appender with the specific name.
    200      *
    201      * @param name The name of the Appender.
    202      */
    203     public void removeAppender(final String name) {
    204         final AppenderControl ctl = appenders.remove(name);
    205         if (ctl != null) {
    206             cleanupFilter(ctl);
    207         }
    208     }
    209
    210     /**
    211      * Returns all Appenders as a Map.
    212      *
    213      * @return a Map with the Appender name as the key and the Appender as the
    214      *         value.
    215      */
    216     public Map<String, Appender> getAppenders() {
    217         final Map<String, Appender> map = new HashMap<String, Appender>();
    218         for (final Map.Entry<String, AppenderControl> entry : appenders
    219                 .entrySet()) {
    220             map.put(entry.getKey(), entry.getValue().getAppender());
    221         }
    222         return map;
    223     }
    224
    225     /**
    226      * Removes all Appenders.
    227      */
    228     protected void clearAppenders() {
    229         waitForCompletion();
    230         final Collection<AppenderControl> controls = appenders.values();
    231         final Iterator<AppenderControl> iterator = controls.iterator();
    232         while (iterator.hasNext()) {
    233             final AppenderControl ctl = iterator.next();
    234             iterator.remove();
    235             cleanupFilter(ctl);
    236         }
    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.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
    285      * LoggerConfig.
    286      *
    287      * @param logEventFactory the LogEventFactory.
    288      */
    289     public void setLogEventFactory(final LogEventFactory logEventFactory) {
    290         this.logEventFactory = logEventFactory;
    291     }
    292
    293     /**
    294      * Returns the valid of the additive flag.
    295      *
    296      * @return true if the LoggerConfig is additive, false otherwise.
    297      */
    298     public boolean isAdditive() {
    299         return additive;
    300     }
    301
    302     /**
    303      * Sets the additive setting.
    304      *
    305      * @param additive true if the LoggerConfig should be additive, false
    306      *            otherwise.
    307      */
    308     public void setAdditive(final boolean additive) {
    309         this.additive = additive;
    310     }
    311
    312     /**
    313      * Returns the value of logger configuration attribute {@code includeLocation},
    314      * or, if no such attribute was configured, {@code true} if logging is
    315      * synchronous or {@code false} if logging is asynchronous.
    316      *
    317      * @return whether location should be passed downstream
    318      */
    319     public boolean isIncludeLocation() {
    320         return includeLocation;
    321     }
    322
    323     /**
    324      * Returns an unmodifiable map with the configuration properties, or
    325      * {@code null} if this {@code LoggerConfig} does not have any configuration
    326      * properties.
    327      * <p>
    328      * For each {@code Property} key in the map, the value is {@code true} if
    329      * the property value has a variable that needs to be substituted.
    330      *
    331      * @return an unmodifiable map with the configuration properties, or
    332      *         {@code null}
    333      * @see Configuration#getStrSubstitutor()
    334      * @see StrSubstitutor
    335      */
    336     // LOG4J2-157
    337     public Map<Property, Boolean> getProperties() {
    338         return properties == null ? null : Collections
    339                 .unmodifiableMap(properties);
    340     }
    341
    342     /**
    343      * Logs an event.
    344      *
    345      * @param loggerName The name of the Logger.
    346      * @param fqcn The fully qualified class name of the caller.
    347      * @param marker A Marker or null if none is present.
    348      * @param level The event Level.
    349      * @param data The Message.
    350      * @param t A Throwable or null.
    351      */
    352     public void log(final String loggerName, final String fqcn,
    353             final Marker marker, final Level level, final Message data,
    354             final Throwable t) {
    355         List<Property> props = null;
    356         if (properties != null) {
    357             props = new ArrayList<Property>(properties.size());
    358
    359             for (final Map.Entry<Property, Boolean> entry : properties.entrySet()) {
    360                 final Property prop = entry.getKey();
    361                 final String value = entry.getValue() ? config.getStrSubstitutor()
    362                         .replace(prop.getValue()) : prop.getValue();
    363                 props.add(Property.createProperty(prop.getName(), value));
    364             }
    365         }
    366         final LogEvent event = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
    367         log(event);
    368     }
    369
    370     /**
    371      * Waits for all log events to complete before shutting down this
    372      * loggerConfig.
    373      */
    374     private void waitForCompletion() {
    375         shutdownLock.lock();
    376         try {
    377             if (shutdown.compareAndSet(false, true)) {
    378                 int retries = 0;
    379                 while (counter.get() > 0) {
    380                     try {
    381                         noLogEvents.await(retries + 1, TimeUnit.SECONDS);
    382                     } catch (final InterruptedException ie) {
    383                         if (++retries > MAX_RETRIES) {
    384                             break;
    385                         }
    386                     }
    387                 }
    388             }
    389         } finally {
    390             shutdownLock.unlock();
    391         }
    392     }
    393
    394     /**
    395      * Logs an event.
    396      *
    397      * @param event The log event.
    398      */
    399     public void log(final LogEvent event) {
    400
    401         counter.incrementAndGet();
    402         try {
    403             if (isFiltered(event)) {
    404                 return;
    405             }
    406
    407             event.setIncludeLocation(isIncludeLocation());
    408
    409             callAppenders(event);
    410
    411             if (additive && parent != null) {
    412                 parent.log(event);
    413             }
    414         } finally {
    415             if (counter.decrementAndGet() == 0) {
    416                 shutdownLock.lock();
    417                 try {
    418                     if (shutdown.get()) {
    419                         noLogEvents.signalAll();
    420                     }
    421                 } finally {
    422                     shutdownLock.unlock();
    423                 }
    424             }
    425         }
    426     }
    427
    428     protected void callAppenders(final LogEvent event) {
    429         for (final AppenderControl control : appenders.values()) {
    430             control.callAppender(event);
    431         }
    432     }
    433
    434
    435     @Override
    436     public String toString() {
    437         return Strings.isEmpty(name) ? "root" : name;
    438     }
    439
    440     /**
    441      * Factory method to create a LoggerConfig.
    442      *
    443      * @param additivity True if additive, false otherwise.
    444      * @param level The Level to be associated with the Logger.
    445      * @param loggerName The name of the Logger.
    446      * @param includeLocation whether location should be passed downstream
    447      * @param refs An array of Appender names.
    448      * @param properties Properties to pass to the Logger.
    449      * @param config The Configuration.
    450      * @param filter A Filter.
    451      * @return A new LoggerConfig.
    452      */
    453     @PluginFactory
    454     public static LoggerConfig createLogger(
    455             @PluginAttribute("additivity") final String additivity,
    456             @PluginAttribute("level") final Level level,
    457             @PluginAttribute("name") final String loggerName,
    458             @PluginAttribute("includeLocation") final String includeLocation,
    459             @PluginElement("AppenderRef") final AppenderRef[] refs,
    460             @PluginElement("Properties") final Property[] properties,
    461             @PluginConfiguration final Configuration config,
    462             @PluginElement("Filter") final Filter filter) {
    463         if (loggerName == null) {
    464             LOGGER.error("Loggers cannot be configured without a name");
    465             return null;
    466         }
    467
    468         final List<AppenderRef> appenderRefs = Arrays.asList(refs);
    469         final String name = loggerName.equals("root") ? Strings.EMPTY : loggerName;
    470         final boolean additive = Booleans.parseBoolean(additivity, true);
    471
    472         return new LoggerConfig(name, appenderRefs, filter, level, additive,
    473                 properties, config, includeLocation(includeLocation));
    474     }
    475
    476     // Note: for asynchronous loggers, includeLocation default is FALSE,
    477     // for synchronous loggers, includeLocation default is TRUE.
    478     protected static boolean includeLocation(final String includeLocationConfigValue) {
    479         if (includeLocationConfigValue == null) {
    480             final boolean sync = !AsyncLoggerContextSelector.class.getName()
    481                     .equals(PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR));
    482             return sync;
    483         }
    484         return Boolean.parseBoolean(includeLocationConfigValue);
    485     }
    486
    487     /**
    488      * The root Logger.
    489      */
    490     @Plugin(name = "root", category = "Core", printObject = true)
    491     public static class RootLogger extends LoggerConfig {
    492
    493         private static final long serialVersionUID = 1L;
    494
    495         @PluginFactory
    496         public static LoggerConfig createLogger(
    497                 @PluginAttribute("additivity") final String additivity,
    498                 @PluginAttribute("level") final Level level,
    499                 @PluginAttribute("includeLocation") final String includeLocation,
    500                 @PluginElement("AppenderRef") final AppenderRef[] refs,
    501                 @PluginElement("Properties") final Property[] properties,
    502                 @PluginConfiguration final Configuration config,
    503                 @PluginElement("Filter") final Filter filter) {
    504             final List<AppenderRef> appenderRefs = Arrays.asList(refs);
    505             final Level actualLevel = level == null ? Level.ERROR : level;
    506             final boolean additive = Booleans.parseBoolean(additivity, true);
    507
    508             return new LoggerConfig(LogManager.ROOT_LOGGER_NAME, appenderRefs,
    509                     filter, actualLevel, additive, properties, config,
    510                     includeLocation(includeLocation));
    511         }
    512     }
    513
    514 }