一、slf4j 日志组件介绍
项目开发,部署运行过程中,记录运行日志的重要性不言而喻。在项目线上运行的过程中,日志基本成为我们了解系统运行状态的唯一工具,因此,了解好Java中常见日志组件的使用以及原理,对我们的项目开发有着重要的意义。
在Java中,经常听到的关于日志的组件有很多,比如:slf4j,commons-logging,log4j,logback,slf4j-simple, jdk自带的java.util.Logging 等等。梳理清楚这些组件之间的关系对于组件的熟练使用来说至关重要。在我们的项目逐渐复杂的过程中,我们可能会越来越多的引入第三方的开发包,这些开发包中也不可避免的会使用一些不同的日志组件,但有没有发现,我们的项目仍然可以正常的打印的日志,正常的运行,不出问题,这就完全得益于Java中日志系统的优良设计模式。
slf4j 、commons-logging和java中的jdbc有点像,这两个组件设计了日志系统的通用接口,其他的日志组件如 log4j 等是日志功能的具体实现。说白了也就是,slf4j定义了该做什么,并指明了这么做需要遵守什么规则,log4j这些组件实现了怎么做,也就是该如何记录日志,并遵守了相关的规则。
slf4j的全称 Simple Logging Facade for Java(简单日志门面),这个组件的设计采用了Facade 设计模式,它为Java程序提供了一个统一的日志输出接口,但是这个组件不提供具体的日志实现方案,因此,如果准备让程序记录日志,还必须要引入具体的实现了日志记录功能的组件,在这里,以 log4j 为例,来为程序实现日志记录功能。
二、slf4j + log4j 实现程序的日志记录功能
2.1 引入相关jar包
要使用这个组合,需要引入的jar包有:slf4j-api-x.x.x.jar,log4j-x.x.x.jar,slf4j-log4j12-x.x.x.jar。slf4j-log4j12是slf4j与log4j之间的桥接包,这个包的存在是和slf4j设计方式、log4j实现思想密切相关的。
jar 包的maven位置坐标如下:
1 | <dependency> |
2.2 创建日志配置文件( log4j.properties)
在项目目录下创建日志的属性文件 log4j.properties(名字自定义),配置好日志文件输出路径,输出级别等信息。
1 | #定义日志输出的级别,输出的位置;主要的输出级别有:DEBUG , INFO , WARN, ERROR , FATAL, ALL。 |
2.3 创建项目demo,输出日志
使用eclipse开发工具创建一个maven项目,pom文件填写好jar地址,log4j.properties放置于项目目录下,创建好demo类,创建log对象,输出日志信息。
项目结构如下图:
测试demo如下,由于设置的日志输出级别是 WARN 级别,因此,logger.info()这个函数不会输出任何信息:
1 | public static void main( String[] args ) |
输出结果如下:
三、 slf4j 组件运行原理分析
slf4j 组件对外提供统一的日志对象访问接口,log4j 这类组件会实现这个接口中的相关标准规定。获取真正的记录日志的Logger对象,都是通过LoggerFactory的getLogger()方法来获取的 :Logger logger = LoggerFactory.getLogger(App.class)。这个方法会bind(绑定)具体的实现了slf4j组件标准的类,所有的实现了slf4j标准的类都必定有一约定好的类:org/slf4j/impl/StaticLoggerBinder.class。通过 loggerFactoryClassLoader.getResources(“org/slf4j/impl/StaticLoggerBinder.class”)获取到类的完整路径,从而获取到具体实现类,然后就会调用这个类(对 log4j 来说就是Log4jLoggerFactory这个类)的getLogger()方法获取到真正记录日志的Logger对象,从而实现日志的记录。*** 注意,在这里不是一定要加载一个类(App.class),但是加载一个类也是有好处的。如果类有了包声明后,在log4j的配置文件中,可以声明属于某个包下的类用什么方式来显示日志,或只显示某个包下的类的日志。
想知道这个方法是如何一步一步获取到不同的组件的 Logger 对象的,我们可以一步一步调试跟踪程序的运行来查看。代码的大致运行逻辑是这样的:1,主程序先调用slf4j组件的方法 Logger logger = LoggerFactory.getLogger(App.class);2,调用slf4j组件的 getILoggerFactory()方法开始获取具体的实现类log4j;3,getILoggerFactory会调用bind方法,bind会再调用findPossibleStaticLoggerBinderPathSet()方法尝试获取实现了标准的类,loggerFactoryClassLoader.getResources(“org/slf4j/impl/StaticLoggerBinder.class”) 会去classpath查找这个类,如果有,说明项目引入了具体的日志组件,可以进行日志的记录,这是会返回具体的对象,否则就会提示没有找到相关的类;4,获取到具体对象后(这里是Log4jLoggerFactory),会调用该对象的getLogger方法,获取到具体的日志对象,至此,开始输出日志,程序结束。
具体的函数实现,可以在调试中看的清清楚楚:
第一步:
第二步:
第三步:
第四步:
第五步:开始绑定可能存在的实现类
第六步:搜索可能存在的实现类
第七步:调动clasLoader的getResources()获取这些具体的类
第八步:获取到log4j的Log4jLoggerFactory,然后调用这个类的getLogger()方法,从而获取Logger对象
到这里之后,就是log4j做的事情了, 和slf4j没太大关系了,再顺着调试的脚步,往下面看看log4j是如何来生成这么一个logger对象的
第九步:Log4jLoggerFactory 的getLogger()方法,这个函数会调用LogManager.getLogger()方法
第十步:Logger在日志系统里面由 Hierarchy 进行统一管理,如果ht中没有存储当前这个类的Logger,则会新建一个,如果有的话,则会进行取出,不会再重新建立一个实例。
Logger对象会返回,这时候就可以调用这个对象的方法进行日志的记录了。至此,调试结束。
四、总结
日志在系统中扮演着一个非常重要的角色,因此,了解好常见的日志系统,对我们的项目有很大的意义。通过日志快速定位到问题所在,也会减少因为某些错误对系统造成巨大的损失。
了解 slf4j 和 log4j 的关系有利于我们加深对 Facade 设计模式的理解,加深对编程的认识。欢迎一起学习交流此方面的知识。