为什么要使用slf4j
现实场景:
我们自己的系统中使用了logback这个日志系统
我们的系统使用了A.jar,A.jar中使用的日志系统为log4j
我们的系统又使用了B.jar,B.jar中使用的日志系统为slf4j-simple
这样,我们的系统就不得不同时支持并维护logback、log4j、slf4j-simple三种日志框架,非常不便。
解决这个问题的方式就是引入一个适配层,由适配层决定使用哪一种日志系统,而调用端只需要做的事情就是打印日志而不需要关心如何打印日志,slf4j或者commons-logging就是这种适配层。
从上面的描述,我们清楚地知道一点:slf4j只是一个日志标准,并不是日志系统的具体实现。
理解这句话非常重要,slf4j只做两件事情:
- 提供日志接口
- 提供获取具体日志对象的方法
slf4j-simple、logback-classic都是slf4j的具体实现,log4j并不直接实现slf4j,但是有专门的一层桥接slf4j-log4j12来实现slf4j。
slf4j实现原理
slf4j的用法就是一句"Logger logger = LoggerFactory.getLogger(Object.class);"
可见这里就是通过LoggerFactory去拿slf4j提供的一个Logger接口的具体实现而已。getLogger的时候会去classpath下找STATIC_LOGGER_BINDER_PATH,即所有slf4j的实现,在提供的jar包路径下,一定是有"org/slf4j/impl/StaticLoggerBinder.class"存在的。
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; private 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; 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; }
我们不能避免在系统中同时引入多个slf4j的实现,所以接收的地方是一个Set。
同时存在多个"org/slf4j/impl/StaticLoggerBinder.class"怎么办?
首先确定的是这不会导致启动报错只会打印warnning,其次在这种情况下编译期间,编译器会选择其中一个StaticLoggerBinder.class进行绑定,sfl4j也在reportActualBinding方法中报告了绑定的是哪个日志框架。
StaticLoggerBinder就比较简单了,不同的StaticLoggerBinder其getLoggerFactory实现不同,拿到ILoggerFactory之后调用一下getLogger即拿到了具体的Logger,可以使用Logger进行日志输出。