相信任何一位工程师都在代码中写过日志打印代码,也知道日志打印对项目的重要性,有人做过统计代码中的日志占到工程总代码比例的4%,良好的日志打印能够帮我们进行代码调试以及快速的定位问题。在java中我们最经常用到的日志框架是log4j和logback,log4j和logback都是对日志接口slf4j的实现版本,而且作者是同一个人,可以说logback是对log4j日志框架的优化版本。logback具有很多优于log4j的特性,比如logback日志打印速度更快,性能损耗更小等等。对于日志的使用相信大家和我有一样的困惑:1. 对于项目中的日志框架自己仅仅是在前人配好的日志之上使用logger打印日志,基本上看不懂日志文件的配置。2.如果让我自己在一个新的工程上配置一个日志框架就无从下手。如果诸位也有这样的困惑可以随我一起来看看这篇logback学习笔记,相信会让大家有所收获。
一. 准备工作
1. 引入logback依赖
正如在序言中所说logback是实现了slf4j日志接口的日志框架。我们需要再maven工程中同时引入slf4j依赖和logback的依赖。这样我们就可以通过slf4j接口来调用logback日志框架。其实log4j也是如此,需要同时引用slf4j和log4j的依赖。
2. 日志打印级别介绍
使用过日志打印的同学应该知道日志框架中都定义了不同级别的日志打印接口,对应不同的日志级别,具有不同的优先级。logback中定义了5中日志打印级别:trace、debug、info、warn、error。trace很少使用;debug可用于代码调试,一般我们可以在调试时用debug打印一些输入和输出参数进行代码调试;info可以打印一些重要的需要打印中间处理数据;warn可以打印一些不影响程序正常运行但是会发生潜在错误的数据;error一般用在程序发生异常时的异常数据打印或者其他错误信息的打印。这几种日志打印优先级:trace < debug < info < warn < error。我们可以对日志框架设置不同的日志级别,如果我们设置了info级别,那么代码中只会输出info以及info以上优先级的日志,debug和trace日志不会输出。日志优先级和日志打印级别的设置为我们提供了很多方便,如在项目上线前的调试阶段我们可以将日志打印级别设置为debug,代码中的debug日志就可以在调试阶段正常输出。在项目调试完成上线后将日志级别改为info或以上级别,就可以屏蔽掉debug日志,避免日志打印过多。
二. logback中的核心概念
一般我们在项目中集成某个日志框架时通常都会通过一个xml配置文件进行日志框架的配置,配置文件方式为我们集成日志打印提供了很多方便。在介绍配置文件集成日志打印之前,本节先介绍一些比较logback中一些比较核心的概念,这将有助于我们对后续对配置文件内容的理解。
1. Logger、Appenders和Layouts
ch.qos.logback.classic.Logger、Appender和Layout是logback中的几个主要类。其中Logger是我们打印日志我们进行日志打印是需要用到的类,它实现了slf4j中定义的Logger接口;Appender是一个接口,用来定义日志的输出位置,比如将日志输出到控制台可以使用ConsoleAppender、输出到文件则可以使用FileAppender等等;Layout则用于定义日志文件的输出格式。这里大家先对这三个核心类有个概念上的认识,后续用到就会很容理解了。
2. logback的日志层级关系
logback中logger对象会自动绑定一个树形的层级关系,每个logger对象都会有一个字符串表示的名字,如果一个logger的名字是另一个logger名字的前缀,那么两个logger之间就具有父子层级关系,其中logback有一个根logger对象,它是所有logger的公共祖先(有些类似树的概念)。初次了解logback层级关系的同学肯定都觉得我这么说很抽象,不知所云,这点我们有共识。通过一个例子来说明就会清晰些,来看例子:
首先,需要注意的是不管我们使用的日志框架时log4j还是logback,只要我们在引入日志框架依赖的同时引入了slf4j依赖,这样我们就使用slf4j中的接口来引用日志实现类对象。这是一个非常好的特性如果我们在项目中想要更换日志框架,只要我们修改日志配置文件即可,不用去修改代码中的日志打印代码。在这里我们定义了rootLooger日志对象,它的名字是"root";然后又定义了一个logger1日志对象,它的名字是"com.logback.demo";最后我们又定义了logger2日志对象,日志的名称是"com.logback.demo.LogbackDemo"。如上定义的rootLogger是logger1和logger2的根,logger1的日志名字是logger2日志名字的前缀,因此logger1是logger2的祖先。需要注意的是这种名字前缀是指使用点分割的名字前缀。如:"com.logback.demo"是"com.logback.demo.LogbackDemo"的前缀,而com不是comon的前缀。也就是说:如果logger1的日志名称是com而logger2的日志名称是comon,那么logger1就不是logger2的祖先。
这种日志层级关系为logback中的日志对象提供了继承性,包括日志打印级别的继承和日志输出位置(Appender)继承。例如:rootLogger中默认定义了日志打印级别为debug,输出到控制台。logger1和logger2就默认继承了rootLogger的这两个属性。我们通过一个简单表格来说明,logback中的继承关系。
如上,root中定义的日志打印级别为debug,则其生效的日志打印级别就为debug。X作为root的子层级,它自己定义了日志打印级别为INFO,那么X从root继承的日志打印级别就不起作用了。对于X.Y来说他的父节点是X,X.Y自己没有定义日志级别,它就从父节点X继承了日志打印级别INFO。对于X.Y.Z也是同样的道理。
这种父层级日志属性向自层级传递是logback中默认提供的机制,logback提供了API来取消这种层级间日志属性的继承。logback的ch.qos.logback.classic.Logger类中有一个bool类型的additive属性,用于控制层级间的继承行为,默认为true。同时提供了一个setAdditive()方法,用于设置additive的值。
做一个实验,通过将logger1引用指向日志对象的additive属性设为false后,logger2的debug将不会在控制台(Console)有内容输出。通过将logger1引用指向的日志对象的additive设置为true后,logger2的debug打印可以正常输出。(logback有一个默认的日志打印实现,即将日志输出到控制台,root的日志打印级别为debug,所以我们不需要指定Appender和Layout即可看到以一定格式输出到控制台的日志内容)
正如我在代码中注释的那样,logger1是一个org.slf4j.Logger(slf4j接口)类型的引用,它实际指向的是一个ch.qos.logback.classic.Logger类型(logback中实现的Logger类)的对象。如果我们想要通过代码的方式调用ch.qos.logback.classic.Logger中特有的接口就需要将org.slf4j.Logger引用强转为ch.qos.logback.classic.Logger。可以看到在代码中操作调用这些API是十分繁琐的,在后续内容中介绍到配置文件的方式就会简单许多。
不知道大家是否有一个疑问:平时我们使用Logger时都是通过像下面这样的方式,LoggerFactory的getLogger()接收的是日志所在类的class实例,并不是传递一个字符串来指定logger对象的名称。这有什么区别呢?实际上这是日志框架给我们提供的一种使用日志的便捷方式,指定类的class实例日志框架会自动将日志对象的名称设为该类的全路径类名,这样我们就可以使用日志就更加方便了,同时能够根据日志名清晰的看出来日志的是在哪个类中打印的。我们可以打印日志时观察一下日志的输出看看是否有类的全路径类名在日志中输出。
3. 代码设置logger的日志打印级别
在前面的内容中我们提到了logback中定义了几个日志打印级别,logback中定义了类ch.qos.logback.classic.Level来对logback中的日志打印级别进行了定义。利用这个类我们可以对logback 日志对象进行日志打印级别的设置。
如果我们将logger1日志对象的setLevel设置为INFO,同时将其additive属性设置为true。logger2.debug又不会有内容输出,这是为何呢?其实很简单:层级继承和日志打印优先级共同作用。因为logger2是logger1的子层级,因此logger2会从logger1继承打印级别的属性。logger2自身并未定义日志打印级别,其生效的日志打印级别即为INFO级别。而debug的日志打印优先级是低于INFO的,因此debug日志不会输出。下面这张图可以比较清晰的说明了在不同日志打印级别下进行不同的日志打印是否有内容输出。
除了设置logger对象的日志打印级别,还可以在代码中设置该logger的Appender,即定义日志的输出位置。默认是输出到控制台(ConsoleAppender)。我们可以通过ch.qos.logback.classic.Logger中的addAppender对象将日志同时输出到其他Appender中,感兴趣的同学可以试试。
4. LoggerContext
LoggerContext是logback中定义的日志上下文,logback中每个logger对象都会绑定LoggerContext上。LoggerContext生成logger对象并维护logger对象的层级关系。就如同Spring框架中的SpringContext一样,我们可以把它理解为组织logback中logger对象的类。当然我们也可以打印LoggerContext对象,看看它都做了哪些工作。
logback学习笔记(上)就先到这里,在logback学习(下)中我们会一起来看一下如何使用配置文件来进行logback日志配置。相信有了本篇介绍内容的铺垫在学习配置文件配置logback相关内容时会更加得心应手。
网友评论