关于slf4j:SLF4J门面日志框架源码探索-京东云技术团队

1 SLF4J介绍SLF4J即Simple Logging Facade for Java,它提供了Java中所有日志框架的简略外观或形象。因而,它使用户可能应用单个依赖项解决任何日志框架,例如:Log4j,Logback和JUL(java.util.logging)。通过在类门路中插入适当的 jar 文件(绑定),能够在部署时插入所需的日志框架。如果要更换日志框架,仅仅替换依赖的slf4j bindings。比方,从java.util.logging替换为log4j,仅仅须要用slf4j-log4j12-1.7.28.jar替换slf4j-jdk14-1.7.28.jar。 2 SLF4J源码剖析咱们通过代码动手,层层加码,直观感触SLF4J打印日志,并跟踪代码寻根究底。次要理解,SLF4J是如何作为门面和其余日志框架进行解耦。 2.1 pom只援用依赖slf4j-api,版本是1.7.30 <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency>2.1.1 执行一个Demopublic class HelloSlf4j { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloSlf4j.class); logger.info("Hello World info"); }}2.1.2 日志提示信息绑定org.slf4j.impl.StaticLoggerBinder失败。如果在类门路上没有找到绑定,那么 SLF4J 将默认为无操作实现 []() 2.1.3 跟踪源码点开办法getLogger(),能够直观看到LoggerFactory应用动态工厂创立Logger。通过以下办法,逐渐点击,报错也很容易找到,能够在bind()办法看到打印的异样日志信息。 org.slf4j.LoggerFactory#getLogger(java.lang.Class<?>) org.slf4j.LoggerFactory#getLogger(java.lang.String) org.slf4j.LoggerFactory#getILoggerFactory org.slf4j.LoggerFactory#performInitialization org.slf4j.LoggerFactory#bind 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()) { staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); } // the next line does the binding StaticLoggerBinder.getSingleton(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; reportActualBinding(staticLoggerBinderPathSet); } 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); } finally { postBindCleanUp(); } }进一步剖析绑定办法findPossibleStaticLoggerBinderPathSet(),能够发现在以后ClassPath下查问了所有该门路的资源“org/slf4j/impl/StaticLoggerBinder.class”,这里可能没有加载到任何文件,也可能绑定多个,对没有绑定和绑定多个的场景进行了敌对提醒。这里通过门路加载资源的目标次要用来对加载的各种异样场景提醒。 ...

June 21, 2023 · 2 min · jiezi

Spring-Boot-2-集成log4j2日志框架

前言Log4j2是 Log4j 的进化版本,并提供了许多 Logback 可用的改进,同时解决了 Logback 体系结构中的一些固有问题。而且日志处理中我们会用到kafka作为日志管道。而kafka客户端依赖与Logback的兼容不是很完美,你可以选择排除依赖冲突或者使用Log4j2 。<!-- more --> 排除Logback依赖Spring Boot 2.x默认使用Logback日志框架,要使用 Log4j2必须先排除 Logback。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <!--排除logback--> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions></dependency>引入Log4j2依赖<!--log4j2 依赖--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId></dependency>上面的 log4j2 已经适配了slf4j日志门面,所以我们的代码无需替换,只需要替换具体的日志框架以及对应的配置文件。 配置Log4j2创建log4j2.xml文件,放在工程resources目录里。这样就可以不加任何配置。如果你需要指定配置文件需要在Spring boot 配置文件application.yml中指定 logging.config 属性。下面是一份比较详细的 log4j2 配置文件 : <?xml version="1.0" encoding="UTF-8"?> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="WARN" monitorInterval="30"> <!--先定义所有的appender--> <appenders> <!--这个输出控制台的配置--> <console name="Console" target="SYSTEM_OUT"> <!--输出日志的格式--> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> </console> <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用--> <File name="log" fileName="log/test.log" append="false"> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/> </File> <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <RollingFile name="RollingFileInfo" fileName="${sys:user.home}/logs/info.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> </RollingFile> <RollingFile name="RollingFileWarn" fileName="${sys:user.home}/logs/warn.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 --> <DefaultRolloverStrategy max="20"/> </RollingFile> <RollingFile name="RollingFileError" fileName="${sys:user.home}/logs/error.log" filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%d{HH:mm:ss:SSS}] [%p] - %l - %m%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> </RollingFile> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <logger name="org.springframework" level="INFO"/> <logger name="org.mybatis" level="INFO"/> <root level="all"> <appender-ref ref="Console"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileError"/> </root> </loggers> </configuration>基本上你拿上面的配置根据你自己的需要更改一下即可生效。 windows 下 ${sys:user.home} 会将日志打印到用户目录下 ...

October 9, 2019 · 2 min · jiezi

一次生产环境单机日志不打印的排查过程

背景Q 业务系统在做发布前检查工作时,发现一台单机的主日志没有打印,而其他生产机器的日志表现则是正常的。 排查流 思考通过 greys 观察主 service 的业务入口,发现该机器线上流量的请求接受、逻辑处理、结果反馈均正常。排查范围就聚焦在 log jar 配置上。 解决SOF搜索,找到一篇 log 绑定包冲突的排查 & 解决方法。排查本地是否存在绑定包冲突的问题,结果发现确实是的 —— 引入 slf4j-log4j12-empty_version.jar,排掉其他 jar 包引入的日志绑定包。发布、部署该单机,问题解决。原理研究了一下 slf4j-log4j12 实现绑定的原理——简单来说,日志输出到单机本地,需要以下 3 类 jar 包相互配合: slf4j 。业务程序调用的日志接口,只有接口定义,没有实现,从而保证了代码层调用 Log 的统一日志组件。比如 log4j-api、log4j-core,是 log4j2 的内部实现;logback-classic、logback-core,是 logback 的内部实现;log4j,是 logj4 的内部实现。它们本身是没有冲突的,可以并存绑定包。将 slf4j-api 的接口绑定到对应的 log 实现上,比如 slf4j-log4j12、log4j-slf4j-impl(关键) 具体到代码实现上,就是通过 StaticLoggerBinder.getSingleton ( ) 方法返回的单例,实现包的绑定 —— qjt 程序中,同时存在着两类绑定包:log4j-slf4j-impl-2.7.jar 和 slf4j-log4j12-1.7.2.jar。这实际是一种意义上的日志绑定包冲突 —— 选定哪个包,取决于 ClassLoader 先加载哪个,具有一定的随机性。 其他生产机 StaticLoggerBinder 的加载情况 —— 可以看到使用的是 log4j-slf4j-impl-2.7.jar ...

June 4, 2019 · 1 min · jiezi

Java日志那些事

日志系统的发展我们日常接触到的日志系统有很多种,log4j,JUL(jdk自带),logback等,我们可以直接根据对象的日志API进行使用。但是考虑到API各不相同,所以出现了JCL(Jakarta Commons Logging)、slf4j等日志API框架。日志API框架只是统一的API,其底层的具体的日志记录工作还是由log4j、JUL、logback等承担。如何选择和搭配日志系统目前来说,新应用使用logback是首选,一些老系统中很可能使用的是log4j等。目前slf4j对logback和log4j都支持,对JCL也提供了桥接方法,将JCL的api转化slf4j的API。贴一张Webx中的图足以说明一切组装日志系统由于存在JCL,SLF4j两大日志框架,logback、log4j、JUL日志系统所以理论上有这么多种日志系统的搭配。JULlog4jlogbackjcl+log4jslf4j+slf4j-log4j12+log4jslf4j+logbackjcl-over-slf4j+slf4j+logbackjcl-over-slf4j+slf4j+slf4j-log4j12+log4j其中slf4j+slf4j-log4j12+log4j、slf4j+logback是主流,推荐使用。多种日志工具共存时的解决方案当依赖了一些三方库时,可能会出现多种日志共存的问题,无法保证每种日志抽象库都使用一样的实现类,此时需要制定固定的日志库。主流的日志库都提供了扩展方式JUL(java.util.logging)通过LogManager.getLogManager().getLogger("").addHandler()方法,可以添加日志具体实现LogManager.getLogManager().getLogger("").addHandler(new SLF4JBridgeHandler());继承java.util.logging.Handler 类,在实现中使用具体的Logger库即可实现例子:jul-to-slf4j(https://www.slf4j.org/api/org…)JCL(org.apache.commons.logging)j通过制定环境变量LogFactory,org.apache.commons.logging.LogFactorySystem.setProperty(“org.apache.commons.logging.LogFactory”,“com.answern.claimv2.framework.log.LogFactoryImplAdapter”);继承org.apache.commons.logging.LogFactory类,实现自己的LogFactory即可还有另外一种暴力的方式:不引入commons-logging包,而是创建jcl的一些同名类,在实现中直接使用具体的日志库。jcl-over-slf4j(https://mvnrepository.com/art…)就是一个典型的例子.相同功能的还有spring-jcl(https://mvnrepository.com/art…)log4jlog4j提供了java对原生spi机制的支持建立MEATA-INF/services/org.apache.logging.log4j.spi.Provider文件继承org.apache.logging.log4j.spi.Provider类,实现自己的LoggerContextFactory即可实现例子:log4j-to-slf4j(https://logging.apache.org/lo…)SLFJ4Jslf4j官方介绍了使用方式,通过引入不同的jar包来使用具体的日志库。由于slf4j拆分做的很好,当多种日志库共存时,若不引入slf4j-xxx.jar时,不会加载相应的日志库。所以若日志冲突时,使用slf4j的三方库只需要include/exclude相应的实现库即可。编写自己的框架/类库时该如何选择日志库由于日志库多种多样,如果盲目引入jcl或者slf4j时,可能会对具体使用的项目造成影响。所以最合适的方式是内嵌一套日志抽象,内部动态的去选择加载哪个日志库。主流的成熟框架都会这么做,尽可能的保持兼容性。例如spring/mybatis/freemarker/dubbo这些框架,都有一套内嵌的日志抽象,打印日志时只需要调用内嵌的日志即可做到全兼容。参考https://www.jianshu.com/p/54c…https://www.slf4j.org/

February 25, 2019 · 1 min · jiezi