【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

相关文章

SpringBoot条件注解核心作用与使用场景详解

《SpringBoot条件注解核心作用与使用场景详解》SpringBoot的条件注解为开发者提供了强大的动态配置能力,理解其原理和适用场景是构建灵活、可扩展应用的关键,本文将系统梳理所有常用的条件注... 目录引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、@ConditionalOn

通过Spring层面进行事务回滚的实现

《通过Spring层面进行事务回滚的实现》本文主要介绍了通过Spring层面进行事务回滚的实现,包括声明式事务和编程式事务,具有一定的参考价值,感兴趣的可以了解一下... 目录声明式事务回滚:1. 基础注解配置2. 指定回滚异常类型3. ​不回滚特殊场景编程式事务回滚:1. ​使用 TransactionT

Spring LDAP目录服务的使用示例

《SpringLDAP目录服务的使用示例》本文主要介绍了SpringLDAP目录服务的使用示例... 目录引言一、Spring LDAP基础二、LdapTemplate详解三、LDAP对象映射四、基本LDAP操作4.1 查询操作4.2 添加操作4.3 修改操作4.4 删除操作五、认证与授权六、高级特性与最佳

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

SpringSecurity JWT基于令牌的无状态认证实现

《SpringSecurityJWT基于令牌的无状态认证实现》SpringSecurity中实现基于JWT的无状态认证是一种常见的做法,本文就来介绍一下SpringSecurityJWT基于令牌的无... 目录引言一、JWT基本原理与结构二、Spring Security JWT依赖配置三、JWT令牌生成与

Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码

《Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码》:本文主要介绍Java中日期时间转换的多种方法,包括将Date转换为LocalD... 目录一、Date转LocalDateTime二、Date转LocalDate三、LocalDateTim

Qt spdlog日志模块的使用详解

《Qtspdlog日志模块的使用详解》在Qt应用程序开发中,良好的日志系统至关重要,本文将介绍如何使用spdlog1.5.0创建满足以下要求的日志系统,感兴趣的朋友一起看看吧... 目录版本摘要例子logmanager.cpp文件main.cpp文件版本spdlog版本:1.5.0采用1.5.0版本主要

如何配置Spring Boot中的Jackson序列化

《如何配置SpringBoot中的Jackson序列化》在开发基于SpringBoot的应用程序时,Jackson是默认的JSON序列化和反序列化工具,本文将详细介绍如何在SpringBoot中配置... 目录配置Spring Boot中的Jackson序列化1. 为什么需要自定义Jackson配置?2.

Java中使用Hutool进行AES加密解密的方法举例

《Java中使用Hutool进行AES加密解密的方法举例》AES是一种对称加密,所谓对称加密就是加密与解密使用的秘钥是一个,下面:本文主要介绍Java中使用Hutool进行AES加密解密的相关资料... 目录前言一、Hutool简介与引入1.1 Hutool简介1.2 引入Hutool二、AES加密解密基础

Spring Boot项目部署命令java -jar的各种参数及作用详解

《SpringBoot项目部署命令java-jar的各种参数及作用详解》:本文主要介绍SpringBoot项目部署命令java-jar的各种参数及作用的相关资料,包括设置内存大小、垃圾回收... 目录前言一、基础命令结构二、常见的 Java 命令参数1. 设置内存大小2. 配置垃圾回收器3. 配置线程栈大小