程序员在旅途

用这生命中的每一秒,给自己一个不后悔的未来!

0%

Java日志组件slf4j使用案例及原理分析

一、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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>

  2.2 创建日志配置文件( log4j.properties)
  在项目目录下创建日志的属性文件 log4j.properties(名字自定义),配置好日志文件输出路径,输出级别等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#定义日志输出的级别,输出的位置;主要的输出级别有:DEBUG , INFO , WARN, ERROR , FATAL, ALL。
#这里定义了 WARN 输出级别,比WARN级别高的ERROR、FATAL级别的日志会输出来,比这个级别低的不会输出来
log4j.rootLogger= WARN, ServerDailyRollingFile, stdout

# 1,配置Appenders输出到文件
log4j.appender.ServerDailyRollingFile=org.apache.log4j.DailyRollingFileAppender
#日期形式
log4j.appender.ServerDailyRollingFile.DatePattern='.'yyyy-MM-dd
# 日志文件保存位置
log4j.appender.ServerDailyRollingFile.File=E:/logs/slf/slf.log
log4j.appender.ServerDailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.ServerDailyRollingFile.layout.ConversionPattern=%d - %m%n
#每日新建一个日志文件
log4j.appender.ServerDailyRollingFile.Append=true

# 2,配置Appenders输出到Console控制台
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} %p [%c] %m%n

  2.3 创建项目demo,输出日志
  使用eclipse开发工具创建一个maven项目,pom文件填写好jar地址,log4j.properties放置于项目目录下,创建好demo类,创建log对象,输出日志信息。
  项目结构如下图:
项目结构图
  测试demo如下,由于设置的日志输出级别是 WARN 级别,因此,logger.info()这个函数不会输出任何信息:

1
2
3
4
5
6
7
public static void main( String[] args )
{
Logger logger = LoggerFactory.getLogger(App.class);
logger.info("slf4j项目使用了日志系统,这里是项目info日志信息");
logger.warn("警告日志信息");
logger.error("错误日志信息输出 {} ",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}

  输出结果如下:
demo运行结果

三、 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 设计模式的理解,加深对编程的认识。欢迎一起学习交流此方面的知识。