slf4j通過提供一個可變的接口,從而使各日志組件可以方便接入自己的實現,從而實現統一市面上各式各樣的日志組件使用。從而減輕大家的開發壓力,即使是下次更換日志組件,也無需更改原有其他代碼。
另外,因為java中中間件大面積使用,也急需一個統一的日志組件格式,但是卻不應該限制用戶使用任何日志組件。所以 slf4j就可以大展身手了!
我們來看看,slf4j是怎樣將各式各樣的組件統一化的:
其實就是通過一個LoggerFactory工廠方法,實現不同的組件接入,獲取logger實例!而logger則是一些通用的接口,對業務方完全統一。
private Logger logger = LoggerFactory.getLogger(this.getClass());
// 通過以上獲取日志組件實例 // org.slf4j.LoggerFactory.getLogger() /** * Return a logger named corresponding to the class passed as parameter, using * the statically bound {@link ILoggerFactory} instance. * * <p>In case the the <code>clazz</code> parameter differs from the name of * the caller as computed internally by SLF4J, a logger name mismatch warning will be * printed but only if the <code>slf4j.detectLoggerNameMismatch</code> system property is * set to true. By default, this property is not set and no warnings will be printed * even in case of a logger name mismatch. * * @param clazz the returned logger will be named after clazz * @return logger * * * @see <a href="http://www.slf4j.org/codes.html#loggerNameMismatch">Detected logger name mismatch</a> */ public static Logger getLogger(Class<?> clazz) { // 獲取logger, 將clazzName 傳入 Logger logger = getLogger(clazz.getName()); if (DETECT_LOGGER_NAME_MISMATCH) { Class<?> autoComputedCallingClass = Util.getCallingClass(); if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName())); Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); } } return logger; } /** * Return a logger named according to the name parameter using the statically * bound {@link ILoggerFactory} instance. * * @param name The name of the logger. * @return logger */ public static Logger getLogger(String name) { // 獲取日志工廠 ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); } /** * Return the {@link ILoggerFactory} instance in use. * <p/> * <p/> * ILoggerFactory instance is bound with this class at compile time. * * @return the ILoggerFactory instance in use */ public static ILoggerFactory getILoggerFactory() { // INITIALIZATION_STATE 狀態保存 if (INITIALIZATION_STATE == UNINITIALIZED) { synchronized (LoggerFactory.class) { if (INITIALIZATION_STATE == UNINITIALIZED) { // 未初始化時進行初始化 INITIALIZATION_STATE = ONGOING_INITIALIZATION; performInitialization(); } } } switch (INITIALIZATION_STATE) { case SUCCESSFUL_INITIALIZATION: return StaticLoggerBinder.getSingleton().getLoggerFactory(); case NOP_FALLBACK_INITIALIZATION: return NOP_FALLBACK_FACTORY; case FAILED_INITIALIZATION: throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); case ONGOING_INITIALIZATION: // support re-entrant behavior. // See also http://jira.qos.ch/browse/SLF4J-97 return SUBST_FACTORY; } throw new IllegalStateException("Unreachable code"); } private final static void performInitialization() { // 綁定 bind(); if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { // 版本檢測 versionSanityCheck(); } } // 綁定 private final static void bind() { try { Set<URL> staticLoggerBinderPathSet = null; // skip check under android, see also http://jira.qos.ch/browse/SLF4J-328 if (!isAndroid()) { // 列舉可能使用的logger的url, 備用 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); // 存在多個實現時,報告存在的歧義 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); } // the next line does the binding // 接下來,已經找到 StaticLoggerBinder 了,就使用其實現的 getSingleton() 方法,返回logger實例 StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; reportActualBinding(staticLoggerBinderPathSet); replayEvents(); } catch (NoClassDefFoundError ncde) { String msg = ncde.getMessage(); if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) { INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\"."); Util.report("Defaulting to no-operation (NOP) logger implementation"); Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details."); } else { failedBinding(ncde); throw ncde; } } catch (java.lang.NoSuchMethodError nsme) { String msg = nsme.getMessage(); if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) { INITIALIZATION_STATE = FAILED_INITIALIZATION; Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding."); Util.report("Your binding is version 1.5.5 or earlier."); Util.report("Upgrade your binding to version 1.6.x."); } throw nsme; } catch (Exception e) { failedBinding(e); throw new IllegalStateException("Unexpected initialization failure", e); } } static Set<URL> findPossibleStaticLoggerBinderPathSet() { // use Set instead of list in order to deal with bug #138 // LinkedHashSet appropriate here because it preserves insertion order during iteration Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>(); try { ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); Enumeration<URL> paths; // We need to use the name of the StaticLoggerBinder class, but we can't reference // the class itself. // private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; // 通過加載 org/slf4j/impl/StaticLoggerBinder 的實現類,來返回該值,即其他日志組件,需要覆蓋該類,從而實現自己的日志組件的接入,當有多個組件時,由類加載時機決定使用 // 如果多個實現,會返回一個列表,由后續邏輯決定使用哪個實現 if (loggerFactoryClassLoader == null) { paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); } else { paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); } while (paths.hasMoreElements()) { URL path = paths.nextElement(); staticLoggerBinderPathSet.add(path); } } catch (IOException ioe) { Util.report("Error getting resources from path", ioe); } return staticLoggerBinderPathSet; } /** * Prints a warning message on the console if multiple bindings were found on the class path. * No reporting is done otherwise. * */ private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) { if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { Util.report("Class path contains multiple SLF4J bindings."); for (URL path : binderPathSet) { Util.report("Found binding in [" + path + "]"); } Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); } } // logback 的實現 getSingleton() 如下: /** * The unique instance of this class. */ private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); private static Object KEY = new Object(); static { SINGLETON.init(); } private boolean initialized = false; private LoggerContext defaultLoggerContext = new LoggerContext(); private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton(); /** * Package access for testing purposes. */ void init() { try { try { new ContextInitializer(defaultLoggerContext).autoConfig(); } catch (JoranException je) { Util.report("Failed to auto configure default logger context", je); } // logback-292 if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) { StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext); } contextSelectorBinder.init(defaultLoggerContext, KEY); initialized = true; } catch (Throwable t) { // we should never get here Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t); } } private StaticLoggerBinder() { // DEFAULT_CONTEXT_NAME = "default"; defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME); } public static StaticLoggerBinder getSingleton() { // 返回 static 創建的實例 return SINGLETON; } // 獲取實例后,報告獲取到的實例 private static void reportActualBinding(Set<URL> binderPathSet) { // binderPathSet can be null under Android if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]"); } } // 綁定事件通知,調用 SubstituteLoggingEvent.log(), 進行消息通知 private static void replayEvents() { final LinkedBlockingQueue<SubstituteLoggingEvent> queue = SUBST_FACTORY.getEventQueue(); final int queueSize = queue.size(); int count = 0; final int maxDrain = 128; List<SubstituteLoggingEvent> eventList = new ArrayList<SubstituteLoggingEvent>(maxDrain); while (true) { int numDrained = queue.drainTo(eventList, maxDrain); if (numDrained == 0) break; for (SubstituteLoggingEvent event : eventList) { replaySingleEvent(event); if (count++ == 0) // 第一個事件通知時,觸發 warning emitReplayOrSubstituionWarning(event, queueSize); } eventList.clear(); } } // 調用 substLogger.log(event) private static void replaySingleEvent(SubstituteLoggingEvent event) { if (event == null) return; SubstituteLogger substLogger = event.getLogger(); String loggerName = substLogger.getName(); if (substLogger.isDelegateNull()) { // 代理為空時,設置 logger 代理為自身 Logger logger = getLogger(loggerName); substLogger.setDelegate(logger); } if (substLogger.isDelegateNOP()) { // nothing to do } else if (substLogger.isDelegateEventAware()) { substLogger.log(event); } else { Util.report(loggerName); } } // warning private static void emitReplayOrSubstituionWarning(SubstituteLoggingEvent event, int queueSize) { if (event.getLogger().isDelegateEventAware()) { emitReplayWarning(queueSize); } else if (event.getLogger().isDelegateNOP()) { // nothing to do } else { emitSubstitutionWarning(); } } private static void emitReplayWarning(int eventCount) { Util.report("A number (" + eventCount + ") of logging calls during the initialization phase have been intercepted and are"); Util.report("now being replayed. These are subject to the filtering rules of the underlying logging system."); Util.report("See also " + REPLAY_URL); } // 版本檢測,檢測不通過時,僅打印異常提示,不返回錯誤 private final static void versionSanityCheck() { try { String requested = StaticLoggerBinder.REQUESTED_API_VERSION; boolean match = false; for (String aAPI_COMPATIBILITY_LIST : API_COMPATIBILITY_LIST) { if (requested.startsWith(aAPI_COMPATIBILITY_LIST)) { match = true; } } if (!match) { Util.report("The requested version " + requested + " by your slf4j binding is not compatible with " + Arrays.asList(API_COMPATIBILITY_LIST).toString()); Util.report("See " + VERSION_MISMATCH + " for further details."); } } catch (java.lang.NoSuchFieldError nsfe) { // given our large user base and SLF4J's commitment to backward // compatibility, we cannot cry here. Only for implementations // which willingly declare a REQUESTED_API_VERSION field do we // emit compatibility warnings. } catch (Throwable e) { // we should never reach here Util.report("Unexpected problem occured during version sanity check", e); } } // 初始化后,獲取日志工廠 public ILoggerFactory getLoggerFactory() { if (!initialized) { // 初始化未完成時,返回默認 context return defaultLoggerContext; } if (contextSelectorBinder.getContextSelector() == null) { throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL); } // 其他情況動態獲取 context return contextSelectorBinder.getContextSelector().getLoggerContext(); } // 最后,由工廠獲取 logger // 如,logback 的 getLogger() , 使用 ConcurrentHashMap 進行保存各記錄類的實例,每次不同的類單獨一個實例 public Logger getLogger(String name) { Logger slf4jLogger = loggerMap.get(name); if (slf4jLogger != null) { return slf4jLogger; } else { org.apache.log4j.Logger log4jLogger; if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) log4jLogger = LogManager.getRootLogger(); else log4jLogger = LogManager.getLogger(name); Logger newInstance = new Log4jLoggerAdapter(log4jLogger); Logger oldInstance = loggerMap.putIfAbsent(name, newInstance); return oldInstance == null ? newInstance : oldInstance; } }
logback 類名縮寫處理:
// 如,logback 的 getLogger() // ch.qos.logback.classic.LoggerContext.getLogger() @Override public final Logger getLogger(final String name) { if (name == null) { throw new IllegalArgumentException("name argument cannot be null"); } // if we are asking for the root logger, then let us return it without // wasting time if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) { return root; } int i = 0; Logger logger = root; // check if the desired logger exists, if it does, return it // without further ado. Logger childLogger = (Logger) loggerCache.get(name); // if we have the child, then let us return it without wasting time if (childLogger != null) { return childLogger; } // if the desired logger does not exist, them create all the loggers // in between as well (if they don't already exist) String childName; while (true) { // 進行類名寫 int h = LoggerNameUtil.getSeparatorIndexOf(name, i); if (h == -1) { childName = name; } else { childName = name.substring(0, h); } // move i left of the last point i = h + 1; synchronized (logger) { childLogger = logger.getChildByName(childName); if (childLogger == null) { childLogger = logger.createChildByName(childName); loggerCache.put(childName, childLogger); incSize(); } } logger = childLogger; if (h == -1) { return childLogger; } } }
這樣 Logger 實例就獲取到了,就可以調用任意的 log() 方法了!