【Java】slf4j 日志

2024-05-24 21:18
文章标签 java 日志 slf4j

本文主要是介绍【Java】slf4j 日志,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

简介

slf4j(Simple Logging Facade for Java)是简单日志门面框架,主要提供了日志接口,不提供实现。使用了Facade设计模式。

与common-logging对比

之前,slf4j的功能由common-logging来完成,二者都是日志框架的抽象层,有什么区别呢?

1.slf4j的日志接口更高效,提供了占位符式的打印日志接口,避免了字符串的拼接代价。同时是否打印日志的逻辑在接口内部实现,效率更高。

2.更重要的,slf4j可以避免common-logging动态查找算法的bug。slf4j是在编译期决定日志实现类的,而common-logging是在运行时通过classloader决定实现类,后者存在一定bug(具体可以google“common-logging classloader issue”)。

总之,能用slf4j就用slf4j。

架构

我们的应用程序直接依赖slf4j的接口,也就是slf4j-api包。这个日志接口层可以由不同的日志包来实现,比如log4j、logback等等。每一种实现要想与接口对接,就要提供适配器,这样就产生了上图中slf4j-xxx.jar包。

使用

考虑maven的场景。理论上,我们需要三个jar包:slf4j-api.jar、具体实现jar和适配jar。但是我们其实只需要引入slf4j-xxx.jar即可,这个jar包会包含另外两个jar包。

例子:构建一个maven项目,其关于日志的依赖只有:

    <dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.7.25</version></dependency>

查看最终的dependency:

可以看到所需的三种jar都被导入。

        String name = "liyao";Logger logger = LoggerFactory.getLogger(App.class);logger.info("name: {}", name);

使用时,占位符式的api十分方便。

实现细节

1.如何定位实现:

这是getLogger()方法:

    public static Logger getLogger(Class<?> clazz) {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;}public static Logger getLogger(String name) {ILoggerFactory iLoggerFactory = getILoggerFactory();return iLoggerFactory.getLogger(name);}public static ILoggerFactory getILoggerFactory() {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-97return SUBST_FACTORY;}throw new IllegalStateException("Unreachable code");}private final static void performInitialization() {bind();if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {versionSanityCheck();}}

最终是在bind()方法处完成的绑定。

    private final static void bind() {try {Set<URL> staticLoggerBinderPathSet = null;// skip check under android, see also// http://jira.qos.ch/browse/SLF4J-328if (!isAndroid()) {staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);}// the next line does the bindingStaticLoggerBinder.getSingleton();INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;reportActualBinding(staticLoggerBinderPathSet);fixSubstituteLoggers();replayEvents();// release all resources in SUBST_FACTORYSUBST_FACTORY.clear();} 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);}}

上面代码很长,其实只有一句“StaticLoggerBinder.getSingleton”完成了加载。看下StaticLoggerBinder的import。

import org.slf4j.impl.StaticLoggerBinder;

也就是这里会在类路径下寻找这个StaticLoggerBinder类。

接着点进去看下这个类的实现:

public class StaticLoggerBinder implements LoggerFactoryBinder {private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();public static String REQUESTED_API_VERSION = "1.6.99";private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();private final ILoggerFactory loggerFactory = new Log4jLoggerFactory();public static final StaticLoggerBinder getSingleton() {return SINGLETON;}private StaticLoggerBinder() {try {Level var1 = Level.TRACE;} catch (NoSuchFieldError var2) {Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");}}public ILoggerFactory getLoggerFactory() {return this.loggerFactory;}public String getLoggerFactoryClassStr() {return loggerFactoryClassStr;}
}

这是一个非接口的类,所以会在编译器决定。其内部实际上负责返回具体日志实现类的工厂类实例。而且这个类已经不是slf4j-api.jar内的类了,而是slf4j-log4j12.jar内的类:

看到这里应该明朗了,每种具体的日志实现类都要提供一个StaticLoggerBinder类,当具体使用时,只需要导入其中一个jar到类路径即可。slf4j-api的getLogger方法就可以通过staticbinder类拿到具体实现类的工厂,进而构建具体日志实现类的实例。由于这里的StaticLoggerBinder类是非interface的,所以在编译器就可以确定,所以这是一种静态绑定。

这与common-logging的classloader运行时加载方式不同。那如果有多个实现类,也就是多个staticbinder类在类路径下呢?这时slf4j会发出警告,具体绑定哪一个是不确定的,与虚拟机具体的执行有关,所以我们需要保证只有一个staticbinder类在类路径下。

2.适配器:

使用slf4j-xxx.jar工厂返回的日志实例其实是一个adapter,其本身不会提供日志功能的实现,而是转交给具体的日志实现类完成,下面是Log4jLoggerAdapter的debug方法:

    public void debug(String format, Object arg1, Object arg2) {if (this.logger.isDebugEnabled()) {FormattingTuple ft = MessageFormatter.format(format, arg1, arg2);this.logger.log(FQCN, Level.DEBUG, ft.getMessage(), ft.getThrowable());}}

可以看到这里转交给了log变量完成,而log变量就是log4j日志实现类的实例。slf4j-xxx.jar仅仅是完成适配。

lombok

最后奉上一个小技巧,我们可以使用@slf4j注解来完成日志变量的定义和实例化,这样就不需要每一次都在类里写Log log = Factory.getLogger()这样的方法了。lombok会为我们自动生成一个log变量,例子:

@slf4j
public class A {public void f(){log.info("name: {}", "ly");}
}

在idea下使用需要再导入一个idea里的lombok插件,这样就不会报编译错误。

这篇关于【Java】slf4j 日志的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/999555

相关文章

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

SpringBoot日志级别与日志分组详解

《SpringBoot日志级别与日志分组详解》文章介绍了日志级别(ALL至OFF)及其作用,说明SpringBoot默认日志级别为INFO,可通过application.properties调整全局或... 目录日志级别1、级别内容2、调整日志级别调整默认日志级别调整指定类的日志级别项目开发过程中,利用日志

Java中的抽象类与abstract 关键字使用详解

《Java中的抽象类与abstract关键字使用详解》:本文主要介绍Java中的抽象类与abstract关键字使用详解,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、抽象类的概念二、使用 abstract2.1 修饰类 => 抽象类2.2 修饰方法 => 抽象方法,没有

SpringBoot 多环境开发实战(从配置、管理与控制)

《SpringBoot多环境开发实战(从配置、管理与控制)》本文详解SpringBoot多环境配置,涵盖单文件YAML、多文件模式、MavenProfile分组及激活策略,通过优先级控制灵活切换环境... 目录一、多环境开发基础(单文件 YAML 版)(一)配置原理与优势(二)实操示例二、多环境开发多文件版

Spring 中的切面与事务结合使用完整示例

《Spring中的切面与事务结合使用完整示例》本文给大家介绍Spring中的切面与事务结合使用完整示例,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录 一、前置知识:Spring AOP 与 事务的关系 事务本质上就是一个“切面”二、核心组件三、完

Java实现远程执行Shell指令

《Java实现远程执行Shell指令》文章介绍使用JSch在SpringBoot项目中实现远程Shell操作,涵盖环境配置、依赖引入及工具类编写,详解分号和双与号执行多指令的区别... 目录软硬件环境说明编写执行Shell指令的工具类总结jsch(Java Secure Channel)是SSH2的一个纯J

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

SpringBoot 获取请求参数的常用注解及用法

《SpringBoot获取请求参数的常用注解及用法》SpringBoot通过@RequestParam、@PathVariable等注解支持从HTTP请求中获取参数,涵盖查询、路径、请求体、头、C... 目录SpringBoot 提供了多种注解来方便地从 HTTP 请求中获取参数以下是主要的注解及其用法:1

HTTP 与 SpringBoot 参数提交与接收协议方式

《HTTP与SpringBoot参数提交与接收协议方式》HTTP参数提交方式包括URL查询、表单、JSON/XML、路径变量、头部、Cookie、GraphQL、WebSocket和SSE,依据... 目录HTTP 协议支持多种参数提交方式,主要取决于请求方法(Method)和内容类型(Content-Ty

深度解析Java @Serial 注解及常见错误案例

《深度解析Java@Serial注解及常见错误案例》Java14引入@Serial注解,用于编译时校验序列化成员,替代传统方式解决运行时错误,适用于Serializable类的方法/字段,需注意签... 目录Java @Serial 注解深度解析1. 注解本质2. 核心作用(1) 主要用途(2) 适用位置3