写在前面:各位看到此博客的小伙伴,如有不对的地方请及时通过私信我或者评论此博客的方式指出,以免误人子弟。多谢!
本来想写一篇博客记录一下springboot中使用logback的,但是感觉一篇不能详细的记录完全,并且springboot的官方文档对日志的使用描述的也不详细,需要有一定基础才能真正知道日志配置到底应该怎么写,而我也是一个基础不扎实的人,所以,在使用springboot日志前,还是先了解一下logback框架吧,这里只记录一下常用的配置,更详细的请看官方文档:http://www.logback.cn/
Logback 构建在三个主要的类上:Logger,Appender 和 Layouts。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。
Logger的主要功能就是有选择的启用或者禁用日志的输出,但是又不会妨碍另一些日志的输出,通过假定一个日志空间,这个空间包含所有可能的日志语句,这些日志语句根据开发人员设定的标准来进行分类。
获取Logger:通过 LoggerFactory.getLogger()可以获取到具体的 logger 实例,如:
Logger logger = LoggerFactory.getLogger(UserService.class);
有效性/等级继承:
Logger 分为TRACE, DEBUG, INFO, WARN, ERROR五个等级,各级别的优先级由低到高排序为:TRACE < DEBUG < INFO < WARN < ERROR。如果一个给定的 logger 没有指定一个层级,那么它就会继承离它最近的一个祖先的层级,root logger 作为 logger 层次结构的最高层。它是一个特殊的 logger,并且在logback框架中为了确保所有的 logger 都有一个层级,root logger 会有一个默认层级 --- DEBUG,但是在springboot中默认的logback日志级别为INFO,看下springboot中logging包下对logback的配置如下,我用的boot版本是2.1.6,其它版本不知是否有不同,
关于日志级别的继承看下官方文档中的几个例子:
Example 1
在这个例子中,只有 root logger 被指定了层级,所以 logger X,X.Y,X.Y.Z 的有效层级都是 DEBUG。
Example 2
在这个例子中,每个 logger 都分配了层级,所以有效层级就是指定的层级。
Example 3
在这个例子中,logger root,X,X.Y.Z 都分别分配了层级。logger X.Y 继承它的父级 logger X。
Example 4
在这个例子中,logger root,X 都分配了层级。logger X.Y,X.Y.Z 的层级继承它们最近的父级 X。
Appender
站在 logback 的角度来说,输出目的地叫做 appender。appender 包括console、file、数据库等。一个 logger 可以有多个 appender。在springboot中通常使用ch.qos.logback.core.ConsoleAppender类将日志输出到控制台,使用ch.qos.logback.core.rolling.RollingFileAppender类将日志写入文件。
参数化日志
一些打印方法可以接收多个传参。这些打印方法的变体主要是为了提高性能以及减少对代码可读性的影响,记录日志经常被提到的一个点是它的计算代价。这是一个合理的考虑,因为一个中等大小的应用都可以产生成千上万的日志。
对于一些 Logger 如下输出日志:
logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
会产生构建消息参数的成本,是因为需要将整数转为字符串,然后再将字符串拼接起来,为了避免构建参数带来的损耗,有一种更好的方式去格式化日志信息。假设 entry 是一个 Object 对象:
Object entry = new SomeObject();
logger.debug("The entry is {}", entry);
只有在需要打印 debug 信息的时候,才会去格式化日志信息,将 '{}' 替换成 entry 的字符串形式。也就是说在这种情况下,如果禁止了日志的打印,也不会有构建参数上的性能消耗。
下面两行输出的结果是一样的,但是一旦禁止日志打印,第二个变量的性能至少比第一个变量好上 30 倍。
logger.debug("The new entry is " + entry + ".");
logger.debug("The new entry is {}", entry);
使用两个参数的例子如下:
logger.debug("The new entry is {}, It replaces {}.", entry, oldEntry);
如果参数太多,可以采用如下的形式:
Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);
介绍完logback基本的概念,来看一下logback的配置,配置前先看一下配置文件的语法,最基本的结构为 <configuration>
元素,包含 0 或多个 <appender>
元素,其后跟 0 或多个 <logger>
元素,其后再跟最多只能存在一个的 <root>
元素。基本结构图如下:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="chapters.configuration" level="INFO" />
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
配置<configuration>:
为了让 logback 能够在配置文件改变的时候自动去扫描,需要在 <configuration>
标签上添加 scan=true
属性。
<configuration scan="true">
...
</configuration>
默认情况下,一分钟扫描一次配置文件,看是否有更改。通过 <configuration>
标签上的 scanPeriod
属性可以指定扫描周期。扫描周期的时间单位可以是毫秒(默认为毫秒。)、秒、分钟或者小时。
<configuration scan="true" scanPeriod="30 seconds"
...
</configuration>
配置< logger>:
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。<logger>仅有一个必选的name属性,
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
配置 <root logger>:
<root>节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性,<root> 元素可以包含 0 或多个 <appender-ref> 元素。
配置 <appender>:
<appender> 需要两个强制的属性 name 与 class。name 属性用来指定 appender 的名字,class 属性需要指定类的全限定名用于实例化。<appender> 元素可以包含 0 或一个 <layout> 元素,0 或多个 <encoder> 元素,0 或多个 <filter> 元素。在springboot中通常使用ch.qos.logback.core.ConsoleAppender类将日志输出到控制台,使用ch.qos.logback.core.rolling.RollingFileAppender类将日志写入文件,注意:在默认的情况下,<appender>是可以重复使用的:<logger>(包括<root>)可以通过附加(<appender-ref ref="" />)到本身的 appender 输出日志,同样的也可以附加到起祖先的身上,并输出日志。因此,如果同一个 appender 附加到多个 logger 身上,那么就导致日志重复打印,你可以设置 additivity = false禁止向上级传递打印信息。
设置<contextName> :
默认这个 logger context 的名字为 "default"。但是你可以通过 <contextName> 设置其它的名字。但是如果设置过一次就不能再设置)。当多个应用输出日志到同一个目的地,设置 logger context 的名字可以更好的区分。
变量定义:
logback 支持变量的定义以及替换,变量有它的作用域。而且,变量可以在配置文件中,外部文件中,外部资源文件中,甚至动态定义。通过<property>标签定义变量,生命name属性,然后通过${}引用,如下:
<property name="USER_NAME" value="/data/logs" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${USER_NAME}/test.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
出了直接定义在日志配置文件中,还可以定义在spring的配置文件中,通过logging.config指定即可:
logging:
config:
USER_HOME: /data/logs
还可以将这些变量放到一个单独的文件中。如在test.properties文件中定义一个变量USER_HOME=/data/logs,然后通过<property>标签的file属性指定这个文件的路径,在使用的时候同样可以通过${}引用,通常我们在 classpath 下定义配置文件。
<property file="F:\project\test.properties"/>
<RollingFileAppender>配置:
在配置<appender>的时候提到可以使用RollingFileAppender将日志写入文件,RollingFileAppender具有轮转日志文件的功能。例如,RollingFileAppender类负责将日志输出到 log.txt 文件,在满足了特定的条件之后,将日志输出到另外一个文件。
在springboot中为了让RollingFileAppender生效,必须同时设置<rollingPolicy>,<rollingPolicy>配置日志滚动策略,即在满足了特定的条件之后,对日志进行指定的处理(新增一个日志文件、删除之前的文件等)。除了<rollingPolicy>外,常用的属性还有file,append,encoder,filter 等。
file:要写入文件的名称。如果文件不存在,则新建。在 windows 平台上:c:/temp/test.log 或者 c:\\temp\\test.log。没有默认值。
append:如果为 true
,日志事件会被追加到文件中,否则的话,文件会被截断。默认为 true。
rollingPolicy:当轮转发生时,指定 RollingFileAppender 的行为,在springboot中SizeAndTimeBasedRollingPolicy是默认的轮转策略。
SizeAndTimeBasedRollingPolicy的配置需要一个强制的属性 fileNamePattern 以及其它的可选属性:
fileNamePattern:该属性定义了轮转时的属性名。它的值应该由文件名加上一个 %d 的占位符。%d 应该包含 java.text.SimpleDateFormat 中规定的日期格式。如果省略掉这个日期格式,那么就默认为 yyyy-MM-dd。轮转周 期是通过 fileNamePattern 推断出来的。
maxFileSize:控制所有归档文件总的大小(默认10M)。当达到这个大小后,旧的归档文件将会被异步的删除。使用这个属性时还 需要设置maxHistory 属性。而且,maxHistory 将会被作为第一条件,该属性作为第二条件。
maxHistory:控制日志文件的保留时间,将会异步删除旧的文件。比如,你指定maxHistory = 6,那么6 天内的归档文件将会保留在文件夹内,大于 6 天的将会被删除。
另外,SizeAndTimeBasedRollingPolicy支持文件自动压缩。如果fileNamePattern以 .gz 或者 .zip 结尾,将会启动这个特性,看一下这个appender:
<appender name="ROLLING" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>mylog.txt</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
注意,除了 %d 之外还有 %i。这两个占位符都是强制要求的。在当前时间还没有到达周期轮转之前,日志文件达到了 maxFileSize
指定的大小,会进行归档,递增索引从 0 开始。
<encoder>配置:
负责两件事,一是把日志信息转换成字节数组,二是把字节数组写入到输出流。encoder中最重要就是pattern节点,它负责控制输出日志的格式,看下如下写法:
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
解释一下:
%d:表示日期
%thread:表示线程名
%-5level:日志级别
%logger:日志输出的类名
%msg:日志输出内容
%n:换行符
另外还有一些其它的属性,看一下这个pattern
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %-40.40logger{39} --- [%15.15(%thread)] : %msg%n</pattern>
%15.15():如果记录的线程字符长度小于15(第一个)则用空格在左侧补齐,如果字符长度大于15(第二个),则从开头开始截断多余的字符, %-40.40():如果记录的logger字符长度小于40(第一个)则用空格在右侧补齐,如果字符长度大于40(第二个),则从开头开始截断多余的字符,可以根据自己需要定制输出,当然也可以使用springboot默认配置,关于springboot对logback的默认配置在下一篇记录。
<filter>配置:
Logback的过滤器基于三值逻辑(ternary logic),允许把它们组装或成链,从而组成任意的复合过滤策略,过滤器的返回值只能是ACCEPT、DENY和NEUTRAL的其中一个。
1.如果返回DENY,那么记录事件立即被抛弃,不再经过剩余过滤器。
2.如果返回NEUTRAL,那么有序列表里的下一个过滤器会接着处理记录事件。
3.如果返回ACCEPT,那么记录事件被立即处理,不再经过剩余过滤器。
过滤器被添加到<Appender> 中,为<Appender> 添加一个或多个过滤器后,可以用任意条件对日志进行过滤。<Appender> 有多个过滤器时,按照配置顺序执行。
常用的过滤器是LevelFilter:
LevelFilter 基于级别来过滤日志事件。如果事件的级别与配置的级别相等,过滤器会根据配置的 onMatch 与 onMismatch 属性,接受或者拒绝事件,它有以下子节点:
1.<level>:设置过滤级别
2.<onMatch>:用于配置符合过滤条件的操作
3.<onMismatch>:用于配置不符合过滤条件的操作
看下logback官网的示例:
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder>
<pattern>
%-4relative [%thread] %-5level %logger{30} - %msg%n
</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
它的含义是:将过滤器的日志级别配置为INFO,所有INFO级别的日志交给appender处理,非INFO级别的日志,被过滤掉。
貌似着一些就够了,基本在使用logback日志框架的时候常用的也就这些了,而且在springboot中使用logback简单的多,因为springboot为我们提供了默认的配置,接下来下一篇在springboot中使用一下logback,并且查看下springboot默认为我们做了哪些配置。