Home | History | Annotate | Download | only in slf4j
      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.Arrays;
     30 import java.util.Enumeration;
     31 import java.util.Iterator;
     32 import java.util.LinkedHashSet;
     33 import java.util.List;
     34 import java.util.Set;
     35 
     36 import org.slf4j.helpers.NOPLoggerFactory;
     37 import org.slf4j.helpers.SubstituteLogger;
     38 import org.slf4j.helpers.SubstituteLoggerFactory;
     39 import org.slf4j.helpers.Util;
     40 import org.slf4j.impl.StaticLoggerBinder;
     41 
     42 /**
     43  * The <code>LoggerFactory</code> is a utility class producing Loggers for
     44  * various logging APIs, most notably for log4j, logback and JDK 1.4 logging.
     45  * Other implementations such as {@link org.slf4j.impl.NOPLogger NOPLogger} and
     46  * {@link org.slf4j.impl.SimpleLogger SimpleLogger} are also supported.
     47  * <p/>
     48  * <p/>
     49  * <code>LoggerFactory</code> is essentially a wrapper around an
     50  * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at
     51  * compile time.
     52  * <p/>
     53  * <p/>
     54  * Please note that all methods in <code>LoggerFactory</code> are static.
     55  *
     56  *
     57  * @author Alexander Dorokhine
     58  * @author Robert Elliot
     59  * @author Ceki G&uuml;lc&uuml;
     60  *
     61  */
     62 public final class LoggerFactory {
     63 
     64     static final String CODES_PREFIX = "http://www.slf4j.org/codes.html";
     65 
     66     static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder";
     67     static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
     68     static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
     69     static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
     70     static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
     71     static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
     72 
     73     static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
     74     static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory could not be successfully initialized. See also " + UNSUCCESSFUL_INIT_URL;
     75 
     76     static final int UNINITIALIZED = 0;
     77     static final int ONGOING_INITIALIZATION = 1;
     78     static final int FAILED_INITIALIZATION = 2;
     79     static final int SUCCESSFUL_INITIALIZATION = 3;
     80     static final int NOP_FALLBACK_INITIALIZATION = 4;
     81 
     82     static int INITIALIZATION_STATE = UNINITIALIZED;
     83     static SubstituteLoggerFactory TEMP_FACTORY = new SubstituteLoggerFactory();
     84     static NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory();
     85 
     86     // Support for detecting mismatched logger names.
     87     static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch";
     88     static boolean DETECT_LOGGER_NAME_MISMATCH = Boolean.getBoolean(DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
     89 
     90     /**
     91      * It is LoggerFactory's responsibility to track version changes and manage
     92      * the compatibility list.
     93      * <p/>
     94      * <p/>
     95      * It is assumed that all versions in the 1.6 are mutually compatible.
     96      */
     97     static private final String[] API_COMPATIBILITY_LIST = new String[] { "1.6", "1.7" };
     98 
     99     // private constructor prevents instantiation
    100     private LoggerFactory() {
    101     }
    102 
    103     /**
    104      * Force LoggerFactory to consider itself uninitialized.
    105      * <p/>
    106      * <p/>
    107      * This method is intended to be called by classes (in the same package) for
    108      * testing purposes. This method is internal. It can be modified, renamed or
    109      * removed at any time without notice.
    110      * <p/>
    111      * <p/>
    112      * You are strongly discouraged from calling this method in production code.
    113      */
    114     static void reset() {
    115         INITIALIZATION_STATE = UNINITIALIZED;
    116         TEMP_FACTORY = new SubstituteLoggerFactory();
    117     }
    118 
    119     private final static void performInitialization() {
    120         bind();
    121         if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
    122             versionSanityCheck();
    123         }
    124     }
    125 
    126     private static boolean messageContainsOrgSlf4jImplStaticLoggerBinder(String msg) {
    127         if (msg == null)
    128             return false;
    129         if (msg.indexOf("org/slf4j/impl/StaticLoggerBinder") != -1)
    130             return true;
    131         if (msg.indexOf("org.slf4j.impl.StaticLoggerBinder") != -1)
    132             return true;
    133         return false;
    134     }
    135 
    136     private final static void bind() {
    137         try {
    138             Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
    139             reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
    140             // the next line does the binding
    141             StaticLoggerBinder.getSingleton();
    142             INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
    143             reportActualBinding(staticLoggerBinderPathSet);
    144             fixSubstitutedLoggers();
    145         } catch (NoClassDefFoundError ncde) {
    146             String msg = ncde.getMessage();
    147             if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
    148                 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
    149                 Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
    150                 Util.report("Defaulting to no-operation (NOP) logger implementation");
    151                 Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
    152             } else {
    153                 failedBinding(ncde);
    154                 throw ncde;
    155             }
    156         } catch (java.lang.NoSuchMethodError nsme) {
    157             String msg = nsme.getMessage();
    158             if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) {
    159                 INITIALIZATION_STATE = FAILED_INITIALIZATION;
    160                 Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
    161                 Util.report("Your binding is version 1.5.5 or earlier.");
    162                 Util.report("Upgrade your binding to version 1.6.x.");
    163             }
    164             throw nsme;
    165         } catch (Exception e) {
    166             failedBinding(e);
    167             throw new IllegalStateException("Unexpected initialization failure", e);
    168         }
    169     }
    170 
    171     static void failedBinding(Throwable t) {
    172         INITIALIZATION_STATE = FAILED_INITIALIZATION;
    173         Util.report("Failed to instantiate SLF4J LoggerFactory", t);
    174     }
    175 
    176     private final static void fixSubstitutedLoggers() {
    177         List<SubstituteLogger> loggers = TEMP_FACTORY.getLoggers();
    178 
    179         if (loggers.isEmpty()) {
    180             return;
    181         }
    182 
    183         Util.report("The following set of substitute loggers may have been accessed");
    184         Util.report("during the initialization phase. Logging calls during this");
    185         Util.report("phase were not honored. However, subsequent logging calls to these");
    186         Util.report("loggers will work as normally expected.");
    187         Util.report("See also " + SUBSTITUTE_LOGGER_URL);
    188         for (SubstituteLogger subLogger : loggers) {
    189             subLogger.setDelegate(getLogger(subLogger.getName()));
    190             Util.report(subLogger.getName());
    191         }
    192 
    193         TEMP_FACTORY.clear();
    194     }
    195 
    196     private final static void versionSanityCheck() {
    197         try {
    198             String requested = StaticLoggerBinder.REQUESTED_API_VERSION;
    199 
    200             boolean match = false;
    201             for (int i = 0; i < API_COMPATIBILITY_LIST.length; i++) {
    202                 if (requested.startsWith(API_COMPATIBILITY_LIST[i])) {
    203                     match = true;
    204                 }
    205             }
    206             if (!match) {
    207                 Util.report("The requested version " + requested + " by your slf4j binding is not compatible with "
    208                                 + Arrays.asList(API_COMPATIBILITY_LIST).toString());
    209                 Util.report("See " + VERSION_MISMATCH + " for further details.");
    210             }
    211         } catch (java.lang.NoSuchFieldError nsfe) {
    212             // given our large user base and SLF4J's commitment to backward
    213             // compatibility, we cannot cry here. Only for implementations
    214             // which willingly declare a REQUESTED_API_VERSION field do we
    215             // emit compatibility warnings.
    216         } catch (Throwable e) {
    217             // we should never reach here
    218             Util.report("Unexpected problem occured during version sanity check", e);
    219         }
    220     }
    221 
    222     // We need to use the name of the StaticLoggerBinder class, but we can't reference
    223     // the class itself.
    224     private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
    225 
    226     private static Set<URL> findPossibleStaticLoggerBinderPathSet() {
    227         // use Set instead of list in order to deal with bug #138
    228         // LinkedHashSet appropriate here because it preserves insertion order during iteration
    229         Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
    230         try {
    231             ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
    232             Enumeration<URL> paths;
    233             if (loggerFactoryClassLoader == null) {
    234                 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
    235             } else {
    236                 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
    237             }
    238             while (paths.hasMoreElements()) {
    239                 URL path = (URL) paths.nextElement();
    240                 staticLoggerBinderPathSet.add(path);
    241             }
    242         } catch (IOException ioe) {
    243             Util.report("Error getting resources from path", ioe);
    244         }
    245         return staticLoggerBinderPathSet;
    246     }
    247 
    248     private static boolean isAmbiguousStaticLoggerBinderPathSet(Set<URL> staticLoggerBinderPathSet) {
    249         return staticLoggerBinderPathSet.size() > 1;
    250     }
    251 
    252     /**
    253      * Prints a warning message on the console if multiple bindings were found on the class path.
    254      * No reporting is done otherwise.
    255      *
    256      */
    257     private static void reportMultipleBindingAmbiguity(Set<URL> staticLoggerBinderPathSet) {
    258         if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
    259             Util.report("Class path contains multiple SLF4J bindings.");
    260             Iterator<URL> iterator = staticLoggerBinderPathSet.iterator();
    261             while (iterator.hasNext()) {
    262                 URL path = (URL) iterator.next();
    263                 Util.report("Found binding in [" + path + "]");
    264             }
    265             Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
    266         }
    267     }
    268 
    269     private static void reportActualBinding(Set<URL> staticLoggerBinderPathSet) {
    270         if (isAmbiguousStaticLoggerBinderPathSet(staticLoggerBinderPathSet)) {
    271             Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]");
    272         }
    273     }
    274 
    275     /**
    276      * Return a logger named according to the name parameter using the statically
    277      * bound {@link ILoggerFactory} instance.
    278      *
    279      * @param name The name of the logger.
    280      * @return logger
    281      */
    282     public static Logger getLogger(String name) {
    283         ILoggerFactory iLoggerFactory = getILoggerFactory();
    284         return iLoggerFactory.getLogger(name);
    285     }
    286 
    287     /**
    288      * Return a logger named corresponding to the class passed as parameter, using
    289      * the statically bound {@link ILoggerFactory} instance.
    290      *
    291      * <p>In case the the <code>clazz</code> parameter differs from the name of
    292      * the caller as computed internally by SLF4J, a logger name mismatch warning will be
    293      * printed but only if the <code>slf4j.detectLoggerNameMismatch</code> system property is
    294      * set to true. By default, this property is not set and no warnings will be printed
    295      * even in case of a logger name mismatch.
    296      *
    297      * @param clazz the returned logger will be named after clazz
    298      * @return logger
    299      *
    300      *
    301      * @see <a href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected logger name mismatch</a>
    302      */
    303     public static Logger getLogger(Class<?> clazz) {
    304         Logger logger = getLogger(clazz.getName());
    305         if (DETECT_LOGGER_NAME_MISMATCH) {
    306             Class<?> autoComputedCallingClass = Util.getCallingClass();
    307             if (nonMatchingClasses(clazz, autoComputedCallingClass)) {
    308                 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
    309                                 autoComputedCallingClass.getName()));
    310                 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
    311             }
    312         }
    313         return logger;
    314     }
    315 
    316     private static boolean nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass) {
    317         return !autoComputedCallingClass.isAssignableFrom(clazz);
    318     }
    319 
    320     /**
    321      * Return the {@link ILoggerFactory} instance in use.
    322      * <p/>
    323      * <p/>
    324      * ILoggerFactory instance is bound with this class at compile time.
    325      *
    326      * @return the ILoggerFactory instance in use
    327      */
    328     public static ILoggerFactory getILoggerFactory() {
    329         if (INITIALIZATION_STATE == UNINITIALIZED) {
    330             INITIALIZATION_STATE = ONGOING_INITIALIZATION;
    331             performInitialization();
    332         }
    333         switch (INITIALIZATION_STATE) {
    334         case SUCCESSFUL_INITIALIZATION:
    335             return StaticLoggerBinder.getSingleton().getLoggerFactory();
    336         case NOP_FALLBACK_INITIALIZATION:
    337             return NOP_FALLBACK_FACTORY;
    338         case FAILED_INITIALIZATION:
    339             throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    340         case ONGOING_INITIALIZATION:
    341             // support re-entrant behavior.
    342             // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
    343             return TEMP_FACTORY;
    344         }
    345         throw new IllegalStateException("Unreachable code");
    346     }
    347 }
    348