SLF4J 快速入门 / 绑定原理
官网: http://www.slf4j.org/
GitHub: https://github.com/qos-ch/slf4j
一、简介
SLF4J(Simple Logging Façade for Java)日志框架,是各种日志框架的简单门面(simple facade)或抽象接口,允许用户部署时选择具体的日志实现。
相较于 JCL 有什么优点:
- 其在设计上简单得多,因此也足够健壮。
- 静态绑定非常简单 但足够有效,解决了困扰 JCL 的类加载器(class loader)问题
- 参数化日志的增强,解决了重要的日志性能问题
- 在 org.slf4j.Logger 接口中,Marker 对象的引入为更进阶的日志系统预留了空间;同时也允许切换回传统的日志系统
二、需求及包引入
要求及说明:
- JDK版本要求:1.5+
- 向后兼容性:
slf4j-api 自身目前是向后兼容所有版本的,意味着可以从 1.0 升至任意更新版本。
但根据slf4j-api版本不同,具体到绑定层上,则可能需要特定版本的绑定。例如 slf4j-api-1.5.6 需使用 slf4j-simple-1.5.6 而 slf4j-simple-1.4.2 将无法工作。 - 包依赖整体逻辑,参考:Java 日志框架概述(slf4j / log4j / JUL / Common-logging(JCL) / logback)
- 所有绑定层/桥接层库,均随 SLF4J 一起发布,可在此处寻找: https://github.com/qos-ch/slf4j
简单示例:引入 SLF4j API 及其实现(以logback为例)
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
三、SLF4J API 简单使用示例
- 声明及获取 LOGGER
private static final Logger LOGGER = LoggerFactory.getLogger(DistrictController.class);
- 打印日志
LOGGER.debug("Attempted to do something in Obj {}", district);
四、更多相关知识
1. 日志等级
分为5个等级,可见 org.slf4j.event
- ERROR
- WARN
- INFO
- DEBUG
- TRACE
2. SLF4J 日志接口设计
- 日志打印接口
slf4j 以”日志等级”作为方法名,并至少接受一个 String 类型的消息描述,如:debug(String msg) debug(String format, Object arg) debug(String msg, Throwable t) …
至于为什么不直接接受 Object ,参考: http://www.slf4j.org/faq.html#string_or_object
- 模板/参数化消息
SLFJ4J 为消息提供了参数化(如下图),以解决debug("hello" + "world")
这种使用方式无论是否启用该等级日志都会进行字符串连接”的问题,避免日志开销(据官方讲30倍的开销)
消息中参数占位符(formatting anchor):{}
例:logger.debug(“Temperature set to {}. Old temperature was {}.”, t, oldT);
更多性能 / 转义 / 匹配规则信息,可参考: http://www.slf4j.org/faq.html#logging_performance
五、绑定实现原理
- 在第一次调用 LoggerFactory#getLogger 时,会尝试调用 LoggerFactory#bind 进行日志工厂的初始化。
- 根据版本不同,实现绑定的方式也不一致。但并非网上所谓的“编译时绑定”这么高深,关于这种说法后面会解释。
绑定这种术语其实本身就是slf4j官方自创的-
在 slf4j 1.8 版本之前:LoggerFactory#bind 基于 COC(Convention over Configuration)的思想,约定大于配置,单纯调用 org.slf4j.impl.StaticLoggerBinder#getSingleton 来初始化。
但实际上 slf4j-api 根本不包含此类,而是由各实现/绑定包(如 slf4j-log4j)来提供
源码如下:import org.slf4j.impl.StaticLoggerBinder private final static void bind() { try { StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; } catch (NoClassDefFoundError ncde) { 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."); } }
这也是为什么官方特别说明,放且仅放一个绑定,不要在类路径上放置多个绑定,因为会冲突。(you simply drop one and only one binding of your choice onto the appropriate class path location. Do not place more than one binding on your class path. Here is a graphical illustration of the general idea)
-
在 slf4j 1.8 之后:采用 Java SPI (Service Provider Interface) 机制
private final static void bind() { List<SLF4JServiceProvider> providersList = findServiceProviders(); if (providersList != null && !providersList.isEmpty()) { PROVIDER = providersList.get(0); PROVIDER.initialize(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; } } private static List<SLF4JServiceProvider> findServiceProviders() { ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class); List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>(); for (SLF4JServiceProvider provider : serviceLoader) { providerList.add(provider); } return providerList; }
以 slf4j-log4j12 为例,基于 SPI 实现了 SLF4JServiceProvider
-
六、常见问题
- 网上为什么说“编译时绑定”
答:虽然有些迷惑的说法,但也并不是毫无道理。原因如下:- 官方的说法自身就很具误导性,感觉是有意的:
“In fact, each SLF4J binding is hardwired at compile time to use one and only one specific logging framework. For example, the slf4j-log4j12-1.7.28.jar binding is bound at compile time to use log4j” - 虽有些不太准确,但 1.8 以前时的“静态绑定”一定程度上也能解释为编译时绑定。
- 官方的说法自身就很具误导性,感觉是有意的:
- 是否应该将类的 Logger 成员声明为静态?
http://www.slf4j.org/faq.html#declared_static
七、相关踩坑
- 引用调用SLF4J API 运行报错
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
答:SLF4J只是接口层,未找到实现则默认的空实现(nop)【1.6 开始】,并打印警告
可引入 SLF4J 实现/绑定层,如:<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> <scop>test</scop> </dependency>
- 在多个参数情况下,若想打印 Throwable 堆栈信息,需注意Throwable必须放在最后一个
例:logger.error(“错误消息:{}”,e.getMessage(),e);
参考:
SLF4J FAQ
Introduction to SLF4J
Slf4j打印异常的堆栈信息