SLF4j 日志组件自动发现原理分析
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() 方法了!
不要害怕今日的苦,你要相信明天,更苦!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?