以前在 日志?聊一聊slf4j吧 这篇文章中聊了下slf4j
。本文也从实际的例子出发,针对logback
的日志配置进行学习。java
logback 官网:https://logback.qos.ch/git
目前尚未看过日志类框架的源码,仅限于如何使用。因此就不说那些“空话”了。最直观的认知是:github
logback
和log4j
是一我的写的springboot
默认使用的日志框架是logback
。logback-core
是其它模块的基础设施,其它模块基于它构建,显然,logback-core
提供了一些关键的通用机制。logback-classic
的地位和做用等同于 Log4J
,它也被认为是 Log4J
的一个改进版,而且它实现了简单日志门面 SLF4J
;而 logback-access
主要做为一个与 Servlet
容器交互的模块,好比说tomcat
或者 jetty
,提供一些与 HTTP
访问相关的功能。web
这部分主要来学习下logback配置文件的一些配置项。算法
先来看这张图,这个结构就是整个logback.xml配置文件的结构。spring
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<property name="glmapper-name" value="glmapper-demo" />
<contextName>${glmapper-name}</contextName>
<appender>
//xxxx
</appender>
<logger>
//xxxx
</logger>
<root>
//xxxx
</root>
</configuration>
复制代码
ps:想使用spring扩展profile支持,要以logback-spring.xml命名,其余如property须要改成springPropertysql
每一个logger
都关联到logger
上下文,默认上下文名称为“default”
。但可使用contextName
标签设置成其余名字,用于区分不一样应用程序的记录数组
用来定义变量值的标签,property
标签有两个属性,name
和value
;其中name
的值是变量的名称,value
的值时变量定义的值。经过property
定义的值会被插入到logger
上下文中。定义变量后,可使“${name}”来使用变量。如上面的xml
所示。tomcat
用来设置某一个包或者具体的某一个类的日志打印级别以及指定appender
。springboot
根logger,也是一种logger,且只有一个level属性
负责写日志的组件,下面会细说
filter实际上是appender里面的子元素。它做为过滤器存在,执行一个过滤器会有返回DENY,NEUTRAL,ACCEPT三个枚举值中的一个。
首先来配置一个很是简单的文件。这里申请下,我使用的是 logback-spring.xml
。和 logback.xml
在properties
上有略微差异。其余都同样。
工程:springboot+web
先来看下项目目录
properties中就是指定了日志的打印级别和日志的输出位置:
#设置应用的日志级别
logging.level.com.glmapper.spring.boot=INFO
#路径
logging.path=./logs
复制代码
<configuration>
<!-- 默认的控制台日志输出,通常生产环境都是后台启动,这个没太大做用 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n</Pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
复制代码
private static final Logger LOGGER =
LoggerFactory.getLogger(HelloController.class);
@Autowired
private TestLogService testLogService;
@GetMapping("/hello")
public String hello(){
LOGGER.info("GLMAPPER-SERVICE:info");
LOGGER.error("GLMAPPER-SERVICE:error");
testLogService.printLogToSpecialPackage();
return "hello spring boot";
}
复制代码
01:50:39.633 INFO com.glmapper.spring.boot.controller.HelloController
- GLMAPPER-SERVICE:info
01:50:39.633 ERROR com.glmapper.spring.boot.controller.HelloController
- GLMAPPER-SERVICE:error
复制代码
上面的就是经过控制台打印出来的,这个时候由于咱们没有指定日志文件的输出,由于不会在工程目录下生产logs
文件夹。
先来看下配置文件:
<configuration>
<!-- 属性文件:在properties文件中找到对应的配置项 -->
<springProperty scope="context" name="logging.path" source="logging.path"/>
<springProperty scope="context" name="logging.level" source="logging.level.com.glmapper.spring.boot"/>
<!-- 默认的控制台日志输出,通常生产环境都是后台启动,这个没太大做用 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n</Pattern>
</encoder>
</appender>
<appender name="GLMAPPER-LOGGERONE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<root level="info">
<appender-ref ref="GLMAPPER-LOGGERONE"/>
</root>
</configuration>
复制代码
这里咱们appender-ref
指定的appender
是GLMAPPER-LOGGERONE
,由于以前没有名字为GLMAPPER-LOGGERONE
的appender
,因此要增长一个name
为GLMAPPER-LOGGERONE
的appender
。
注意上面这个配置,咱们是直接接将root
的appender-ref
直接指定到咱们的GLMAPPER-LOGGERONE
这个appender的。因此控制台中将只会打印出bannar以后就啥也不打印了,全部的启动信息都会被打印在日志文件glmapper-loggerone.log
中。
可是实际上咱们不但愿个人业务日志中会包括这些启动信息。因此这个时候咱们就须要经过logger
标签来搞事情了。将上面的配置文件进行简单修改:
<logger name="com.glmapper.spring.boot.controller" level="${logging.level}" additivity="false">
<appender-ref ref="GLMAPPER-LOGGERONE" />
</logger>
<root level="${logging.level}">
<appender-ref ref="STDOUT"/>
</root>
复制代码
让root
指向控制台输出;logger
负责打印包com.glmapper.spring.boot.controller
下的日志。
仍是经过咱们的测试controller来打印日志为例,可是这里不会在控制台出现日志信息了。指望的日志文件在./logs/glmapper-spring-boot/glmapper-loggerone.log
。
上面两种是一个基本的配置方式,经过上面两个案例,咱们先来了解下logger/appender/root
之间的关系,而后再详细的说下logger
和appender
的配置细节。
在最前面介绍中提到,root
是根logger
,因此他两是一回事;只不过root
中不能有name
和additivity
属性,是有一个level
。
appender
是一个日志打印的组件,这里组件里面定义了打印过滤的条件、打印输出方式、滚动策略、编码方式、打印格式等等。可是它仅仅是一个打印组件,若是咱们不使用一个logger
或者root
的appender-ref
指定某个具体的appender
时,它就没有什么意义。
所以appender
让咱们的应用知道怎么打、打印到哪里、打印成什么样;而logger
则是告诉应用哪些能够这么打。例如某个类下的日志可使用这个appender
打印或者某个包下的日志能够这么打印。
这里以上面案例中的名为GLMAPPER-LOGGERONE
的appender
说明:
<appender name="GLMAPPER-LOGGERONE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
复制代码
appender
有两个属性 name
和class
;name
指定appender
名称,class
指定appender
的全限定名。上面声明的是名为GLMAPPER-LOGGERONE
,class
为ch.qos.logback.core.rolling.RollingFileAppender
的一个appender
。
<append>true</append>
复制代码
若是是 true
,日志被追加到文件结尾,若是是false
,清空现存文件,默认是true
。
在简介中提到了filter
;做用就是上面说的。能够为appender
添加一个或多个过滤器,能够用任意条件对日志进行过滤。appender
有多个过滤器时,按照配置顺序执行。
临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL
;当日志级别低于临界值时,日志会被拒绝。
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
复制代码
级别过滤器,根据日志级别进行过滤。若是日志级别等于配置级别,过滤器会根据onMath
(用于配置符合过滤条件的操做) 和 onMismatch
(用于配置不符合过滤条件的操做)接收或拒绝日志。
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
复制代码
关于NEUTRAL
、ACCEPT
、DENY
见上文简介中关于filter
的介绍。
file
标签用于指定被写入的文件名,能够是相对目录,也能够是绝对目录,若是上级目录不存在会自动建立,没有默认值。
<file>
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
</file>
复制代码
这个表示当前appender将会将日志写入到${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
这个目录下。
这个子标签用来描述滚动策略的。这个只有appender
的class
是RollingFileAppender
时才须要配置。这个也会涉及文件的移动和重命名(a.log->a.log.2018.07.22)。
最经常使用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。这个下面又包括了两个属性:
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名:按天回滚 daily -->
<FileNamePattern>
${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}
</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
复制代码
上面的这段配置代表天天生成一个日志文件,保存30天的日志文件
根据固定窗口算法重命名文件的滚动策略。
对记录事件进行格式化。它干了两件事:
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}
- %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
复制代码
目前encoder
只有PatternLayoutEncoder
一种类型。
<!-- 错误日志 appender : 按照天天生成日志文件 -->
<appender name="ERROR-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<!-- 过滤器,只记录 error 级别的日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>error</level>
</filter>
<!-- 日志名称 -->
<file>${logging.path}/glmapper-spring-boot/glmapper-error.log</file>
<!-- 天天生成一个日志文件,保存30天的日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名:按天回滚 daily -->
<FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-error.log.%d{yyyy-MM-dd}</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!-- 编码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
复制代码
<!-- 默认的控制台日志输出,通常生产环境都是后台启动,这个没太大做用 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<Pattern>%d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n</Pattern>
</encoder>
</appender>
复制代码
<logger name="com.glmapper.spring.boot.controller" level="${logging.level}" additivity="false">
<appender-ref ref="GLMAPPER-LOGGERONE" />
</logger>
复制代码
上面的这个配置文件描述的是:com.glmapper.spring.boot.controller
这个包下的${logging.level}
级别的日志将会使用GLMAPPER-LOGGERONE
来打印。logger
有三个属性和一个子标签:
logger
约束的某一个包或者具体的某一个类。TRACE
, DEBUG
, INFO
, WARN
, ERROR
, ALL
和 OFF
),还有一个值INHERITED
或者同义词NULL
,表明强制执行上级的级别。若是没有设置此属性,那么当前logger
将会继承上级的级别。logger
传递打印信息。默认是true
。appender-ref
则是用来指定具体appender
的。
在前面的例子中咱们有三种appender,一个是指定包约束的,一个是控制error级别的,一个是控制台的。而后这小节咱们就来实现下不一样日志打印到不一样的log文件中。
这个例子里咱们将com.glmapper.spring.boot.controller
中的日志输出到glmapper-controller.log
;将com.glmapper.spring.boot.service
中的日志输出到glmapper-service.log
。
<!--打印日志到glmapper-service.log的appender-->
<appender name="GLMAPPER-SERVICE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>
${logging.path}/glmapper-spring-boot/glmapper-service.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-service.log.%d{yyyy-MM-dd}</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!--打印日志到glmapper-controller.log的appender-->
<appender name="GLMAPPER-CONTROLLER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>
${logging.path}/glmapper-spring-boot/glmapper-controller.log
</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-controller.log.%d{yyyy-MM-dd}</FileNamePattern>
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!--此logger约束将.controller包下的日志输出到GLMAPPER-CONTROLLER,错误日志输出到GERROR-APPENDE;GERROR-APPENDE见上面-->
<logger name="com.glmapper.spring.boot.controller" level="${logging.level}" additivity="false">
<appender-ref ref="GLMAPPER-CONTROLLER" />
<appender-ref ref="GERROR-APPENDER" />
</logger>
<!--此logger约束将.service包下的日志输出到GLMAPPER-SERVICE,错误日志输出到GERROR-APPENDE;GERROR-APPENDE见上面-->
<logger name="com.glmapper.spring.boot.service" level="${logging.level}" additivity="false">
<appender-ref ref="GLMAPPER-SERVICE" />
<appender-ref ref="GERROR-APPENDER" />
</logger>
复制代码
来看运行结果
一、glmaper-controller
二、glmapper-service
三、glmapper-error
知足咱们的预期,可是这里有个小问题。在info
日志里出现了error
,固然这是正常的。假如咱们不想在info
里面出现error
怎么办呢?很简单,咱们以APPENDER-SERVICE
为例,将filter
过滤器进行修改:
将下面的:
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
复制代码
修改成:
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<!-- 若是命中就禁止这条日志 -->
<onMatch>DENY</onMatch>
<!-- 若是没有命中就使用这条规则 -->
<onMismatch>ACCEPT</onMismatch>
</filter>
复制代码
这里同时要注意的是,在logger
中level
须要设置为info
级别。
这个其实也是和上面那个差不过,只不过粒度更细一点,通常状况下好比说咱们有个定时任务类须要单独来记录其日志信息,这样咱们就能够考虑使用基于类维度来约束打印。
<!--特殊功能单独appender 例如调度类的日志-->
<appender name="SCHEDULERTASKLOCK-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>${logging.path}/glmapper-spring-boot/scheduler-task-lock.log</file>
<!-- 天天生成一个日志文件,保存30天的日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名:按天回滚 daily -->
<FileNamePattern>${logging.path}/glmapper-spring-boot/scheduler-task-lock.log.%d{yyyy-MM-dd}</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!-- 编码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--这里指定到了具体的某一个类-->
<logger name="com.glmapper.spring.boot.task.TestLogTask" level="${logging.level}" additivity="true">
<appender-ref ref="SCHEDULERTASKLOCK-APPENDER" />
<appender-ref ref="ERROR-APPENDER" />
</logger>
复制代码
最终TestLogTask
中的日志将会被打印到这个本身独立的log文件中。以下所示:
logger
的name
除了类、包等约束以外,固然还能够这样来玩。。。
在进行案例以前,这里先把前面案例中logger
声明的代码贴一下,以做对比,以TestLogTask
类中的日志为例:
private static final Logger LOGGER =
LoggerFactory.getLogger(TestLogTask.class);
复制代码
在getLogger
中咱们是将当前对象的class
做为参数的,这个是为了打印时获取其全限定名的(见下面3-)。
1-2018-07-21 11:15:42.003 [pool-1-thread-1]
2-INFO
3-com.glmapper.spring.boot.task.TestLogTask -
4-com.glmapper.spring.boot.task:info
复制代码
咱们一样是service
包下定义一个类TestLogNameServiceImpl
package com.glmapper.spring.boot.service;
@Service("testLogNameService")
public class TestLogNameServiceImpl implements TestLogNameService {
private static final Logger LOGGER =
LoggerFactory.getLogger("GLMAPPER-TEST-LOG");
@Override
public void print() {
LOGGER.info("GLMAPPER-TEST-LOG:this is special logger-----info");
LOGGER.error("GLMAPPER-TEST-LOG:this is special logger-------error");
}
}
复制代码
<appender name="ROOT-APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender">
<append>true</append>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${logging.level}</level>
</filter>
<file>${logging.path}/glmapper-spring-boot/glmapper-test.log</file>
<!-- 天天生成一个日志文件,保存30天的日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名:按天回滚 daily -->
<FileNamePattern>${logging.path}/glmapper-spring-boot/glmapper-test.log.%d{yyyy-MM-dd}
</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<!-- 编码 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--这里的name和业务类中的getLogger中的字符串是同样的-->
<logger name="GLMAPPER-TEST-LOG" level="${logging.level}" additivity="true">
<appender-ref ref="ROOT-APPENDER" />
<appender-ref ref="ERROR-APPENDER" />
</logger>
复制代码
咱们这个预期的是TestLogNameServiceImpl
中的日志不打印到glmapper-service.log
中,而是打印到glmapper-test.log
中。
一、glmapper-test.log
二、glmapper-service.log
知足咱们的预期。
这个仍是比较坑的。为何。看下这个:
<settings>
<setting name="logImpl" value="slf4j" />
</settings>
复制代码
在mybatis-configration.xml
中,咱们经过这样一个配置项来关联到具体的日志组件。可是logImpl
的实现中是没有logback
的。那么怎么办呢?这里只能经过slf4j
的方式桥接到logback
。
而后在咱们的logback-spring.xml中进行以下配置:
<!-- 将sql语句输出到具体的日志文件中 -->
<logger name="com.alipay.sofa.cloudplatform.common.dao" level="${logging.sql.level}" additivity="false">
<appender-ref ref="SQL-APPENDER"/>
</logger>
复制代码
这里有几个点须要注意的。首先是${logging.sql.level}
这个必须是debug,这个是由mybatis自己实现决定的。而这里的name
设定的com.alipay.sofa.cloudplatform.common.dao
值就是咱们dao接口的包路径。
网上看了一个比较典型的案例,这种方式只能输出到控制台,并不能将文件输出到日志文件;它是根据内部的一个实现机制偷了个懒。mybatis用logback日志不显示sql的解决办法。
本篇博客主要是整理最近工做中的一些日志配置积累,将每一个细节进行总结一下,以做备忘。若是有时间的话会考虑看一个日志框架的源码。其实我以为仍是颇有必要的,日志组件毕竟是须要进行日志文件落盘的,这个会涉及到许多的性能问题、缓冲区问题、队列问题、固然还有一些锁的问题、同步打印或者异步打印等问题。有兴趣的小伙伴能够看看,而后分享给咱们。
后面准备写一写蚂蚁金服SOFABoot和SpringBoot的一些文章,若是有兴趣能够先看一波。