View Javadoc

    1   /**
    2    * Copyright (c) 2004-2011 QOS.ch
    3    * All rights reserved.
    4    *
    5    * Permission is hereby granted, free  of charge, to any person obtaining
    6    * a  copy  of this  software  and  associated  documentation files  (the
    7    * "Software"), to  deal in  the Software without  restriction, including
    8    * without limitation  the rights to  use, copy, modify,  merge, publish,
    9    * distribute,  sublicense, and/or sell  copies of  the Software,  and to
    10   * permit persons to whom the Software  is furnished to do so, subject to
    11   * the following conditions:
    12   *
    13   * The  above  copyright  notice  and  this permission  notice  shall  be
    14   * included in all copies or substantial portions of the Software.
    15   *
    16   * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
    17   * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
    18   * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
    19   * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    20   * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    21   * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
    22   * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    23   *
    24   */
    25  package org.slf4j;
    26
    27  import java.io.IOException;
    28  import java.net.URL;
    29  import java.util.ArrayList;
    30  import java.util.Arrays;
    31  import java.util.Enumeration;
    32  import java.util.LinkedHashSet;
    33  import java.util.List;
    34  import java.util.Set;
    35  import java.util.concurrent.LinkedBlockingQueue;
    36
    37  import org.slf4j.event.SubstituteLoggingEvent;
    38  import org.slf4j.helpers.NOPLoggerFactory;
    39  import org.slf4j.helpers.SubstituteLogger;
    40  import org.slf4j.helpers.SubstituteLoggerFactory;
    41  import org.slf4j.helpers.Util;
    42  import org.slf4j.impl.StaticLoggerBinder;
    43
    44  /**
    45   * The <code>LoggerFactory</code> is a utility class producing Loggers for
    46   * various logging APIs, most notably for log4j, logback and JDK 1.4 logging.
    47   * Other implementations such as {@link org.slf4j.impl.NOPLogger NOPLogger} and
    48   * {@link org.slf4j.impl.SimpleLogger SimpleLogger} are also supported.
    49   * <p/>
    50   * <p/>
    51   * <code>LoggerFactory</code> is essentially a wrapper around an
    52   * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at
    53   * compile time.
    54   * <p/>
    55   * <p/>
    56   * Please note that all methods in <code>LoggerFactory</code> are static.
    57   *
    58   *
    59   * @author Alexander Dorokhine
    60   * @author Robert Elliot
    61   * @author Ceki G&uuml;lc&uuml;
    62   *
    63   */
    64  public final class LoggerFactory {
    65
    66      static final String CODES_PREFIX = "http://www.slf4j.org/codes.html";
    67
    68      static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder";
    69      static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
    70      static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
    71      static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
    72      static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
    73      static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
    74      static final String REPLAY_URL = CODES_PREFIX + "#replay";
    75
    76      static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
    77      static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also " + UNSUCCESSFUL_INIT_URL;
    78
    79      static final int UNINITIALIZED = 0;
    80      static final int ONGOING_INITIALIZATION = 1;
    81      static final int FAILED_INITIALIZATION = 2;
    82      static final int SUCCESSFUL_INITIALIZATION = 3;
    83      static final int NOP_FALLBACK_INITIALIZATION = 4;
    84
    85      static volatile int INITIALIZATION_STATE = UNINITIALIZED;
    86      static final SubstituteLoggerFactory SUBST_FACTORY = new SubstituteLoggerFactory();
    87      static final NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory();
    88
    89      // Support for detecting mismatched logger names.
    90      static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch";
    91      static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
    92
    93      static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
    94
    95      /**
    96       * It is LoggerFactory's responsibility to track version changes and manage
    97       * the compatibility list.
    98       * <p/>
    99       * <p/>
    100      * It is assumed that all versions in the 1.6 are mutually compatible.
    101      */
    102     static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.6", "1.7" };
    103
    104     // private constructor prevents instantiation
    105     private LoggerFactory() {
    106     }
    107
    108     /**
    109      * Force LoggerFactory to consider itself uninitialized.
    110      * <p/>
    111      * <p/>
    112      * This method is intended to be called by classes (in the same package) for
    113      * testing purposes. This method is internal. It can be modified, renamed or
    114      * removed at any time without notice.
    115      * <p/>
    116      * <p/>
    117      * You are strongly discouraged from calling this method in production code.
    118      */
    119     static void reset() {
    120         INITIALIZATION_STATE = UNINITIALIZED;
    121     }
    122
    123     private final static void performInitialization() {
    124         bind();
    125         if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
    126             versionSanityCheck();
    127         }
    128     }
    129
    130     private static boolean messageContainsOrgSlf4jImplStaticLoggerBinder(String msg) {
    131         if (msg == null)
    132             return false;
    133         if (msg.contains("org/slf4j/impl/StaticLoggerBinder"))
    134             return true;
    135         if (msg.contains("org.slf4j.impl.StaticLoggerBinder"))
    136             return true;
    137         return false;
    138     }
    139
    140     private final static void bind() {
    141         try {
    142             Set<URL> staticLoggerBinderPathSet = null;
    143             // skip check under android, see also
    144             // http://jira.qos.ch/browse/SLF4J-328
    145             if (!isAndroid()) {
    146                 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
    147                 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
    148             }
    149             // the next line does the binding
    150             StaticLoggerBinder.getSingleton();
    151             INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
    152             reportActualBinding(staticLoggerBinderPathSet);
    153             fixSubstituteLoggers();
    154             replayEvents();
    155             // release all resources in SUBST_FACTORY
    156             SUBST_FACTORY.clear();
    157         } catch (NoClassDefFoundError ncde) {
    158             String msg = ncde.getMessage();
    159             if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
    160                 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
    161                 Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
    162                 Util.report("Defaulting to no-operation (NOP) logger implementation");
    163                 Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
    164             } else {
    165                 failedBinding(ncde);
    166                 throw ncde;
    167             }
    168         } catch (java.lang.NoSuchMethodError nsme) {
    169             String msg = nsme.getMessage();
    170             if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
    171                 INITIALIZATION_STATE = FAILED_INITIALIZATION;
    172                 Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
    173                 Util.report("Your binding is version 1.5.5 or earlier.");
    174                 Util.report("Upgrade your binding to version 1.6.x.");
    175             }
    176             throw nsme;
    177         } catch (Exception e) {
    178             failedBinding(e);
    179             throw new IllegalStateException("Unexpected initialization failure", e);
    180         }
    181     }
    182
    183     private static void fixSubstituteLoggers() {
    184         synchronized (SUBST_FACTORY) {
    185             SUBST_FACTORY.postInitialization();
    186             for (SubstituteLogger substLogger : SUBST_FACTORY.getLoggers()) {
    187                 Logger logger = getLogger(substLogger.getName());
    188                 substLogger.setDelegate(logger);
    189             }
    190         }
    191
    192     }
    193
    194     static void failedBinding(Throwable t) {
    195         INITIALIZATION_STATE = FAILED_INITIALIZATION;
    196         Util.report("Failed to instantiate SLF4J LoggerFactory", t);
    197     }
    198
    199     private static void replayEvents() {
    200         final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_FACTORY.getEventQueue();
    201         final int queueSize = queue.size();
    202         int count = 0;
    203         final int maxDrain = 128;
    204         List<SubstituteLoggingEvent> eventList = new ArrayList<SubstituteLoggingEvent>(maxDrain);
    205         while (true) {
    206             int numDrained = queue.drainTo(eventList, maxDrain);
    207             if (numDrained == 0)
    208                 break;
    209             for (SubstituteLoggingEvent event : eventList) {
    210                 replaySingleEvent(event);
    211                 if (count++ == 0)
    212                     emitReplayOrSubstituionWarning(event, queueSize);
    213             }
    214             eventList.clear();
    215         }
    216     }
    217
    218     private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) {
    219         if (event.getLogger().isDelegateEventAware()) {
    220             emitReplayWarning(queueSize);
    221         } else if (event.getLogger().isDelegateNOP()) {
    222             // nothing to do
    223         } else {
    224             emitSubstitutionWarning();
    225         }
    226     }
    227
    228     private static void replaySingleEvent(SubstituteLoggingEvent event) {
    229         if (event == null)
    230             return;
    231
    232         SubstituteLogger substLogger = event.getLogger();
    233         String loggerName = substLogger.getName();
    234         if (substLogger.isDelegateNull()) {
    235             throw new IllegalStateException("Delegate logger cannot be null at this state.");
    236         }
    237
    238         if (substLogger.isDelegateNOP()) {
    239             // nothing to do
    240         } else if (substLogger.isDelegateEventAware()) {
    241             substLogger.log(event);
    242         } else {
    243             Util.report(loggerName);
    244         }
    245     }
    246
    247     private static void emitSubstitutionWarning() {
    248         Util.report("The following set of substitute loggers may have been accessed");
    249         Util.report("during the initialization phase. Logging calls during this");
    250         Util.report("phase were not honored. However, subsequent logging calls to these");
    251         Util.report("loggers will work as normally expected.");
    252         Util.report("See also " + SUBSTITUTE_LOGGER_URL);
    253     }
    254
    255     private static void emitReplayWarning(int eventCount) {
    256         Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are");
    257         Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system.");
    258         Util.report("See also " + REPLAY_URL);
    259     }
    260
    261     private final static void versionSanityCheck() {
    262         try {
    263             String requested = StaticLoggerBinder.REQUESTED_API_VERSION;
    264
    265             boolean match = false;
    266             for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) {
    267                 if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) {
    268                     match = true;
    269                 }
    270             }
    271             if (!match) {
    272                 Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
    273                                 + Arrays.asList(API_COMPATIBILITY_LIST).toString());
    274                 Util.report("See " + VERSION_MISMATCH + " for further details.");
    275             }
    276         } catch (java.lang.NoSuchFieldError nsfe) {
    277             // given our large user base and SLF4J's commitment to backward
    278             // compatibility, we cannot cry here. Only for implementations
    279             // which willingly declare a REQUESTED_API_VERSION field do we
    280             // emit compatibility warnings.
    281         } catch (Throwable e) {
    282             // we should never reach here
    283             Util.report("Unexpected problem occured during version sanity check", e);
    284         }
    285     }
    286
    287     // We need to use the name of the StaticLoggerBinder class, but we can't
    288     // reference
    289     // the class itself.
    290     private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
    291
    292     static Set<URL> findPossibleStaticLoggerBinderPathSet() {
    293         // use Set instead of list in order to deal with bug #138
    294         // LinkedHashSet appropriate here because it preserves insertion order
    295         // during iteration
    296         Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
    297         try {
    298             ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
    299             Enumeration<URL> paths;
    300             if (loggerFactoryClassLoader == null) {
    301                 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
    302             } else {
    303                 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
    304             }
    305             while (paths.hasMoreElements()) {
    306                 URL path = paths.nextElement();
    307                 staticLoggerBinderPathSet.add(path);
    308             }
    309         } catch (IOException ioe) {
    310             Util.report("Error getting resources from path", ioe);
    311         }
    312         return staticLoggerBinderPathSet;
    313     }
    314
    315     private static boolean isAmbiguousStaticLoggerBinderPathSet(Set<URL> binderPathSet) {
    316         return binderPathSet.size() > 1;
    317     }
    318
    319     /**
    320      * Prints a warning message on the console if multiple bindings were found
    321      * on the class path. No reporting is done otherwise.
    322      *
    323      */
    324     private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
    325         if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
    326             Util.report("Class path contains multiple SLF4J bindings.");
    327             for (URL path : binderPathSet) {
    328                 Util.report("Found binding in [" + path + "]");
    329             }
    330             Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
    331         }
    332     }
    333
    334     private static boolean isAndroid() {
    335         String vendor = Util.safeGetSystemProperty(JAVA_VENDOR_PROPERTY);
    336         if (vendor == null)
    337             return false;
    338         return vendor.toLowerCase().contains("android");
    339     }
    340
    341     private static void reportActualBinding(Set<URL> binderPathSet) {
    342         // binderPathSet can be null under Android
    343         if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
    344             Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");
    345         }
    346     }
    347
    348     /**
    349      * Return a logger named according to the name parameter using the
    350      * statically bound {@link ILoggerFactory} instance.
    351      *
    352      * @param name
    353      *            The name of the logger.
    354      * @return logger
    355      */
    356     public static Logger getLogger(String name) {
    357         ILoggerFactory iLoggerFactory = getILoggerFactory();
    358         return iLoggerFactory.getLogger(name);
    359     }
    360
    361     /**
    362      * Return a logger named corresponding to the class passed as parameter,
    363      * using the statically bound {@link ILoggerFactory} instance.
    364      *
    365      * <p>
    366      * In case the the <code>clazz</code> parameter differs from the name of the
    367      * caller as computed internally by SLF4J, a logger name mismatch warning
    368      * will be printed but only if the
    369      * <code>slf4j.detectLoggerNameMismatch</code> system property is set to
    370      * true. By default, this property is not set and no warnings will be
    371      * printed even in case of a logger name mismatch.
    372      *
    373      * @param clazz
    374      *            the returned logger will be named after clazz
    375      * @return logger
    376      *
    377      *
    378      * @see <a
    379      *      href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected
    380      *      logger name mismatch</a>
    381      */
    382     public static Logger getLogger(Class<?> clazz) {
    383         Logger logger = getLogger(clazz.getName());
    384         if (DETECT_LOGGER_NAME_MISMATCH) {
    385             Class<?> autoComputedCallingClass = Util.getCallingClass();
    386             if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
    387                 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
    388                                 autoComputedCallingClass.getName()));
    389                 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
    390             }
    391         }
    392         return logger;
    393     }
    394
    395     private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) {
    396         return !autoComputedCallingClass.isAssignableFrom(clazz);
    397     }
    398
    399     /**
    400      * Return the {@link ILoggerFactory} instance in use.
    401      * <p/>
    402      * <p/>
    403      * ILoggerFactory instance is bound with this class at compile time.
    404      *
    405      * @return the ILoggerFactory instance in use
    406      */
    407     public static ILoggerFactory getILoggerFactory() {
    408         if (INITIALIZATION_STATE == UNINITIALIZED) {
    409             synchronized (LoggerFactory.class) {
    410                 if (INITIALIZATION_STATE == UNINITIALIZED) {
    411                     INITIALIZATION_STATE = ONGOING_INITIALIZATION;
    412                     performInitialization();
    413                 }
    414             }
    415         }
    416         switch (INITIALIZATION_STATE) {
    417         case SUCCESSFUL_INITIALIZATION:
    418             return StaticLoggerBinder.getSingleton().getLoggerFactory();
    419         case NOP_FALLBACK_INITIALIZATION:
    420             return NOP_FALLBACK_FACTORY;
    421         case FAILED_INITIALIZATION:
    422             throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    423         case ONGOING_INITIALIZATION:
    424             // support re-entrant behavior.
    425             // See also http://jira.qos.ch/browse/SLF4J-97
    426             return SUBST_FACTORY;
    427         }
    428         throw new IllegalStateException("Unreachable code");
    429     }
    430 }