当前位置: 首页 > 科技观察

Java日志之Slf4j、Log4J、Logback原理总结

时间:2023-03-18 18:44:53 科技观察

几乎所有的应用都必然需要日志。那么,面对五花八门的日志框架和配置,我们该何去何从呢?1、前言:在研究mybatis源码的过程中,我意识到需要了解日志的原理,因为mybatis(以及其他一些开源框架,例如rocketmq)都有自己的日志系统,它们都在框架内使用他们自己的日志API,那么他们为什么不像我们通常那样配置一个log4j呢?我不知道根本原因,但我猜测可能是因为某些原因,这些框架比较老,并没有像slf4j这样的事实标准。另一方面,有一些特殊的定制日志。深入研究了mybatis的日志系统,个人觉得这块设计的不是很好。至少在今天看来,不是很优雅,因为本来一个slf4j就可以搞定一切,只好在源码中加上自己的org.apache.ibatis。日志包包含一些适配器。代码虽然不复杂,但是有点冗余。2.原理:slf4j是一个标准,一个门面。它为用户提供了统一的API,并在下方对接各种日志框架。这有点类似于JVM。我们Java开发人员使用统一的API,JVM与各种操作系统接口。严格来说,slf4j本身并没有提供日志的具体实现。图片来自:https://www.cnblogs.com/hanszhao/p/9754419.html3.slf4j使用SPI机制,规定了一个标准的目录结构:org.slf4j.impl.StaticLoggerBinder,但是第三方框架必须有这么一个类用来和slf4j建立关系,比如slf4j-simple.jar,logback,这两个直接实现了slf4j的接口,而对于log4j,需要一个中间适配器slf4j-log4j12。所以在调用slf4j的Loggerlogger=LoggerFactory.getLogger(XXX.class)时,虽然使用了slf4j的API,但实际的日志输出是具体的日志框架。这样做的好处是,当某天要更改日志框架时,只需要替换具体日志框架的jar包,无需更改任何一行代码就可以切换日志框架。4、slf4j如何发现具体日志框架引以为豪的spi机制。前面提到,每个日志框架都需要有一个org.slf4j.impl.StaticLoggerBinder类,log4j使用中间适配器slf4j-log4j12。当调用LoggerFactory.getLogger时,它将在类路径中搜索StaticLoggerBinder类。如果不存在或者存在多个,都会报错。类路径有并且只能有一个StaticLoggerBinder类。5、分析mybatis的日志框架:mybatis有自己的日志系统,日志api为:Loglog=LogFactory.getLog(xxx.class)。同时封装了几个主流的日志框架适配器,包括:SLF4J|LOG4J|日志4J2|JDK_日志|COMMONS_LOGGING|标准输出记录|NO_LOGGING,当调用Loglog=LogFactory.getLog(xxx.class)时,会初始化多个适配器中的一个,可以通过mybatis配置文件中的logImpl指定具体的一个,如果不指定,默认使用SLF4J,因为LogFactory类中的第一个静态代码是SLF4J:static{tryImplementation(LogFactory::useSlf4jLogging);tryImplementation(LogFactory::useCommonsLogging);tryImplementation(LogFactory::useLog4J2Logging);tryImplementation(LogFactory::useLog4JLogging);tryImplementation(LogFactory::useJdkLogging);tryImplementation(LogFactory::useNoLogging);}假设使用默认配置,Slf4jImpl类将被初始化。这个类里面有一个代理日志,这个代理日志就是Loggerlogger=LoggerFactory.getLogger(clazz),这又回到了slf4j的标准使用方法。Mybatis打印日志,其实就是代理对象在打印,代理对象就是classpath中配置的具体日志框架。6、分析log4j是如何与slf4j集成的:前面提到,要使用log4j,必须导入slf4j-log4j12这个jar包,这个jar包中还有一个StaticLoggerBinder类。当我们调用LoggerFactory.getLogger(clazz)时,同样初始化StaticLoggerBinder,然后使用ILoggerFactory创建一个log4jLogger实例,代码如下:publicclassLog4jLoggerFactoryimplementsILoggerFactory{//key:name(String),value:aLog4jLoggerAdapter;ConcurrentMaploggerMap;publicLog4jLoggerFactory(){loggerMap=newConcurrentHashMap();}/**(非Javadoc)**@seeorg.slf4j.ILoggerFactory#getLogger(java.lang.String)*/publicLoggergetLogger(Stringname){Loggerslf4jLogger=loggerMap.get(名称);if(slf4jLogger!=null){returnslf4jLogger;}else{org.apache.log4j.Loggerlog4jLogger;if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))log4jLogger=LogManager.getRootLogger();elselog4jLogger=LogManager.getLogger(name);LoggernewInstance=newLog4jLoggerAdapter(log4jLogger);LoggeroldInstance=loggerMap.putIfAbsent(name,newInstance);returnoldInstance==null?newInstance:oldInstance;}}}最关键的是第二行第7行LoggernewInstance=newLog4jLoggerAdapter(log4jLogger),slf4j的Logger对象其实是一个log4j适配器对象(也是一个代理对象),当slf4j调用debug方法时,它实际上是一个代理对象(也就是真正的log4j对象))正在调用调试方法