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ülcü 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 LoggerFactory()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 */ reset()114 static void reset() { 115 INITIALIZATION_STATE = UNINITIALIZED; 116 TEMP_FACTORY = new SubstituteLoggerFactory(); 117 } 118 performInitialization()119 private final static void performInitialization() { 120 bind(); 121 if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { 122 versionSanityCheck(); 123 } 124 } 125 messageContainsOrgSlf4jImplStaticLoggerBinder(String msg)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 bind()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 failedBinding(Throwable t)171 static void failedBinding(Throwable t) { 172 INITIALIZATION_STATE = FAILED_INITIALIZATION; 173 Util.report("Failed to instantiate SLF4J LoggerFactory", t); 174 } 175 fixSubstitutedLoggers()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 versionSanityCheck()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 findPossibleStaticLoggerBinderPathSet()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 isAmbiguousStaticLoggerBinderPathSet(Set<URL> staticLoggerBinderPathSet)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 */ reportMultipleBindingAmbiguity(Set<URL> staticLoggerBinderPathSet)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 reportActualBinding(Set<URL> staticLoggerBinderPathSet)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 */ getLogger(String name)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 */ getLogger(Class<?> clazz)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 nonMatchingClasses(Class<?> clazz, Class<?> autoComputedCallingClass)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 */ getILoggerFactory()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