待分析的日志规约
- 【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
SLF4J如何保证统一的日志处理方式
SLF4J中的Logger是一个接口即完全抽象,所以SLF4J(Simple Logging Facade For Java)是一个门面模式的日志框架,而不是具体的日志系统。而抽象可以带来很多好处,比如:
1、具体项目中根据需求来使用哪种日志系统,也有可能项目在使用过程中因为一些特定的日志功能会切换到其它日志系统。
2、作为库的输出方,方便客户在整个项目中实现统一的日志处理方式。
3、作为项目的开发者,如果使用抽象,也能实现在自己项目中编写的各个类的日志处理统一。
从SLF4J的全称来看,它是一种外观模式。为什么这么说呢,因为使用了统一的Logger接口去操纵日志接口,避免了对日志系统各个模块单独的调用。
本文的讲解用的日志系统是log4j。那么使用slf4j和log4j,我们需要引入如下三个库。其中org.slf4j是抽象库,完全不依赖具体实现库。slf4j-log4j12为log4j和slf4j之间的桥梁。log4j为具体日志系统。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
</dependency>
那么具体看看SLF4J是如何做到的呢?在具体查看源代码前,或许会有如下跟我一样的想法。
1、SLF4J的logger需要直接或间接指向具体日志系统的logger。
2、LoggerFactory是如何应用工厂模式的。
3、LoggerFactory.getLogger如何保证不会创建来至多个日志系统的logger,各个日志系统的logger是否都由同一工厂创建。
4、由于slf4j库不会引用具体的日志系统,那么LoggerFactory.getLogger是如何创建出不同日志系统的logger的呢。
5、不同日志系统往往会由不同公司开发,所以日志接口会与SLF4J存在偏差。
从查看源代码找出了我的相关顾虑:
1、LoggerFactory没有继承额外的类和实现接口,所以LoggerFactory.getLogger是一个工厂方法,且是简单工厂。
2、调试中LoggerFactory.getLogger调用了bind方法,且StaticLoggerBinder.getSingleton()会获得针对具体日志系统的logger工厂类。这完全得益于JVM的类加载机制,JVM会加载仅且一个StaticLoggerBinder类,且这个类在slf4j-log4j12中。可见JVM类加载的威力。
private static final void bind() {
String msg;
try {
Set e = null;
if(!isAndroid()) {
e = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(e);
}
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = 3;
reportActualBinding(e);
fixSubstituteLoggers();
replayEvents();
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError var2) {
3、为了适配SLF4J logger接口,StaticLoggerBinder会创建具体日志系统的适配器。这里用到了适配器模式。
如何处理项目依赖的第三方库中依赖的各种日志系统
SLF4J中使用了桥接模式来转换各种日志系统到对SLF4J抽象的使用,以便在项目中达到统一日志系统的目的。同时它也能迅速把已有项目切换到对SLF4J抽象的轨道中来。详见:https://www.slf4j.org/legacy.html
为了转换第三方库依赖的日志系统到对SLF4J抽象,以Log4J为例,如下:
<dependency>
<groupId>me.yixt</groupId>
<<artifactId>learning</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
注意使用时需要先移除对log4j的引用,然后再引用log4j-over-slf4j,所以log4j-over-slf4j包含了log4j的接口。
为什么说它是桥接模式呢?因为原来引用的日志系统可以和桥接后的日志系统独立变化。
在log4j-over-slf4j中,Logger会继承Category,而Category正好组合了对SLF4J Logger的引用。
public class Category {
private static final String CATEGORY_FQCN = Category.class.getName();
private String name;
protected Logger slf4jLogger;
private LocationAwareLogger locationAwareLogger;
private static Marker FATAL_MARKER = MarkerFactory.getMarker("FATAL");
Category(String name) {
this.name = name;
this.slf4jLogger = LoggerFactory.getLogger(name);
if(this.slf4jLogger instanceof LocationAwareLogger) {
this.locationAwareLogger = (LocationAwareLogger)this.slf4jLogger;
}
}
遗留问题
1、若项目中使用了SLF4J,且有多个日志系统的存在,因JVM可能加载了不是期望的日志系统,如何处理这种问题。
2、是否可以想出不同于SLF4J的抽象日志框架。