    001/**
    002 * Copyright (c) 2004-2011 QOS.ch
    003 * All rights reserved.
    004 *
    005 * Permission is hereby granted, free  of charge, to any person obtaining
    006 * a  copy  of this  software  and  associated  documentation files  (the
    007 * "Software"), to  deal in  the Software without  restriction, including
    008 * without limitation  the rights to  use, copy, modify,  merge, publish,
    009 * distribute,  sublicense, and/or sell  copies of  the Software,  and to
    010 * permit persons to whom the Software  is furnished to do so, subject to
    011 * the following conditions:
    012 *
    013 * The  above  copyright  notice  and  this permission  notice  shall  be
    014 * included in all copies or substantial portions of the Software.
    015 *
    016 * THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
    017 * EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
    018 * MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
    019 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    020 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    021 * OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
    022 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    023 *
    024 */
    025package org.slf4j;
    026
    027import java.io.IOException;
    028import java.net.URL;
    029import java.util.ArrayList;
    030import java.util.Arrays;
    031import java.util.Enumeration;
    032import java.util.LinkedHashSet;
    033import java.util.List;
    034import java.util.Set;
    035import java.util.concurrent.LinkedBlockingQueue;
    036
    037import org.slf4j.event.SubstituteLoggingEvent;
    038import org.slf4j.helpers.NOPLoggerFactory;
    039import org.slf4j.helpers.SubstituteLogger;
    040import org.slf4j.helpers.SubstituteLoggerFactory;
    041import org.slf4j.helpers.Util;
    042import org.slf4j.impl.StaticLoggerBinder;
    043
    044/**
    045 * The <code>LoggerFactory</code> is a utility class producing Loggers for
    046 * various logging APIs, most notably for log4j, logback and JDK 1.4 logging.
    047 * Other implementations such as {@link org.slf4j.impl.NOPLogger NOPLogger} and
    048 * {@link org.slf4j.impl.SimpleLogger SimpleLogger} are also supported.
    049 * <p/>
    050 * <p/>
    051 * <code>LoggerFactory</code> is essentially a wrapper around an
    052 * {@link ILoggerFactory} instance bound with <code>LoggerFactory</code> at
    053 * compile time.
    054 * <p/>
    055 * <p/>
    056 * Please note that all methods in <code>LoggerFactory</code> are static.
    057 *
    058 *
    059 * @author Alexander Dorokhine
    060 * @author Robert Elliot
    061 * @author Ceki G&uuml;lc&uuml;
    062 *
    063 */
    064public final class LoggerFactory {
    065
    066    static final String CODES_PREFIX = "http://www.slf4j.org/codes.html";
    067
    068    static final String NO_STATICLOGGERBINDER_URL = CODES_PREFIX + "#StaticLoggerBinder";
    069    static final String MULTIPLE_BINDINGS_URL = CODES_PREFIX + "#multiple_bindings";
    070    static final String NULL_LF_URL = CODES_PREFIX + "#null_LF";
    071    static final String VERSION_MISMATCH = CODES_PREFIX + "#version_mismatch";
    072    static final String SUBSTITUTE_LOGGER_URL = CODES_PREFIX + "#substituteLogger";
    073    static final String LOGGER_NAME_MISMATCH_URL = CODES_PREFIX + "#loggerNameMismatch";
    074    static final String REPLAY_URL = CODES_PREFIX + "#replay";
    075
    076    static final String UNSUCCESSFUL_INIT_URL = CODES_PREFIX + "#unsuccessfulInit";
    077    static final String UNSUCCESSFUL_INIT_MSG = "org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also " + UNSUCCESSFUL_INIT_URL;
    078
    079    static final int UNINITIALIZED = 0;
    080    static final int ONGOING_INITIALIZATION = 1;
    081    static final int FAILED_INITIALIZATION = 2;
    082    static final int SUCCESSFUL_INITIALIZATION = 3;
    083    static final int NOP_FALLBACK_INITIALIZATION = 4;
    084
    085    static volatile int INITIALIZATION_STATE = UNINITIALIZED;
    086    static final SubstituteLoggerFactory SUBST_FACTORY = new SubstituteLoggerFactory();
    087    static final NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory();
    088
    089    // Support for detecting mismatched logger names.
    090    static final String DETECT_LOGGER_NAME_MISMATCH_PROPERTY = "slf4j.detectLoggerNameMismatch";
    091    static final String JAVA_VENDOR_PROPERTY = "java.vendor.url";
    092
    093    static boolean DETECT_LOGGER_NAME_MISMATCH = Util.safeGetBooleanSystemProperty(DETECT_LOGGER_NAME_MISMATCH_PROPERTY);
    094
    095    /**
    096     * It is LoggerFactory's responsibility to track version changes and manage
    097     * the compatibility list.
    098     * <p/>
    099     * <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}