一. 整合背景
请移步log4j架构的介绍文章: 《一个著名的日志系统是怎么设计出来的?》
每个日志框架系统都有期优劣之处, logback作为log4j 原作者的升级版本, 存在诸多优势:
- Logback同样是由Log4j的作者设计完成的,拥有更好的特性,用来取代Log4j的一个日志框架,是Slf4j的原生实现(Native implementations)
- 使用日志框架的最佳实践是选择一款日志门面+一款日志实现,这里选择Slf4j+Logback, Slf4j作者也是Logback的作者
- SpringBoot从1.4版本开始,内置的日志框架就是Logback
- 方便的配置日志输出至console, 并同时在磁盘上以文件形式留存,还可以按日志Level 来分别生产日志文件夹, 如: info/debug/error/warn,日志可以按照配置规则, 方面的进行按天/时/分钟不同粒度的归档操作.
logback在springboot maven继承树中的位置:
①. pom.xml 中parent
<parent>标签作用:
.
现在有这样一个场景,有两个web项目A、B,一个java项目C,它们都需要用到同一个jar包:common.jar。如果分别在三个项目的pom文件中定义各自对common.jar的依赖,那么当common.jar的版本发生变化时,三个项目的pom文件都要改,项目越多要改的地方就越多,很麻烦。
这时候就需要用到parent标签, 我们创建一个parent项目,打包类型为pom,parent项目中不存放任何代码,只是管理多个项目之间公共的依赖。在parent项目的pom文件中定义对common.jar的依赖,ABC三个子项目中只需要定义,parent标签中写上parent项目的pom坐标就可以引用到common.jar了。
`
同理, springboot项目构建时, 也用到了这一特性, 会从parent继承过来通用的配置.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>test-project</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>test-project</name>
<description>Demo project for Spring Boot</description>
②. 查看其上一层的依赖(按住ctrl + 鼠标点击artifactId), 如下:
由下面的artifactId中的名字(spring-boot-dependencies)可以看出, 这里包含的都是spring-boot项目所需的公共依赖, 不存放任何代码.
依赖文件名: spring-boot-dependencies-2.1.6.RELEASE.pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
<artifactId>spring-boot-starter-parent</artifactId>
<packaging>pom</packaging>
<name>Spring Boot Starter Parent</name>
<description>Parent pom providing dependency and plugin management for applications
built with Maven</description>
<url>https://projects.spring.io/spring-boot/#/spring-boot-starter-parent</url>
# 向下翻, 可以看到 spring-boot-starter-logging 这个依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
③. 继续查看(spring-boot-starter-logging) 其上一层的依赖(按住ctrl + 鼠标点击artifactId), 如下:
依赖文件名: spring-boot-starter-logging-2.1.6.RELEASE.pom
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.11.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
<version>1.7.26</version>
<scope>compile</scope>
</dependency>
</dependencies>
可以清楚的看到, logback/ log4j-to-slf4j 门面转换器 / jul-to-slf4j 门面转换器, 都已包含在spring-boot的公共依赖中.
④. pom.xml 依赖调用链示意图如下:
二. 实现目标
SpringBoot项目在启动时, 如果未加额外的日志配置, 则默认日志会输出到控制台(Console)中.
以下实验希望实现如下目标:
①. 日志不仅需要打印在控制台, 还需要输入到文件系统中;
②. 文件系统的输出日志, 需要区分 INFO 和 ERROR 级别, 并分别生产对应的日志文件;
③. 制定文件日志的滚动策略, 如按天滚动生成日志;
④. 控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每日滚动,
且 是10,则只保存最近10天的日志文件,删除10日之前的旧文件;
三. 整合的实现
3.1 logback的文件命名
方式 | 说明 |
---|---|
官方推荐 | 默认读取resources目录下的 logback-spring.xml |
自定义位置 | application.yml中配置: ( logging.config: classpath:log/logback-spring.xml ; logging.level.root: info ) |
3.2 logback-spring.xml配置文件详解
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds" debug="true">
<!--<include resource="org/springframework/boot/logging/logback/base.xml" />-->
<contextName>springboot2_logback</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义变量后,可以使“${}”来使用变量。 -->
<!-- 若value="logs" 这种形式则在项目根目录下创建一个叫logs的文件夹 -->
<!-- 若value="/logs" 这种形式则在系统根目录下创建一个叫logs的文件夹 -->
<property name="log.path" value="./logs" />
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>info</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--输出到文件-->
<!-- 时间滚动输出 level为 DEBUG 日志 -->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_debug.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档 -->
<fileNamePattern>${log.path}/debug/log-debug-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 INFO 日志 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_info.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd-HH-mm}.%i.log</fileNamePattern> <!-- 这里测试时,一分钟生成一个日志文件 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 WARN 日志 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_warn.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录warn级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 时间滚动输出 level为 ERROR 日志 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${log.path}/log_error.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}/error/log-error-%d{yyyy-MM-dd-HH-mm}.%i.log</fileNamePattern> <!-- 这里测试时,一分钟生成一个日志文件 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、以及指定<appender>。
<logger>仅有一个name属性,一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,还有一个特殊值INHERITED或者同义词NULL,代表强制执行上级的级别。如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
-->
<!--<logger name="org.springframework.web" level="info"/>-->
<!--<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>-->
<!-- 关闭kafka日志 -->
<logger name="org.apache.kafka.clients.consumer.ConsumerConfig" level="off" />
<logger name="org.apache.kafka" level="off" />
<!-- 标签作用: 获取到项目所处的环境是线上还是线下,根据这个来指定部分日志是否记录 -->
<!-- 开发环境:打印控制台 -->
<springProfile name="dev">
<!--<!–<logger name="com.nmys.view" level="debug"/>–>-->
<!--<!–<logger name="com.rsi.rc" level="debug"/>–>-->
<!-- 这一行如果放开的话,则判断如果是dev开发环境, 则console里只打印error级别的日志 -->
<logger name="com.example.springboot2_logback.scheduler.MyTestScheduler" level="error"/>
</springProfile>
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
不能设置为INHERITED或者同义词NULL。默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->
<!-- ref="CONSOLE" 引用的就是24行定义的,24行注释然后这里引用了的话就会报错,上面和这里都不写的话控制台就不输出,只输出到文件里 -->
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="DEBUG_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="WARN_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
<!--生产环境:输出到文件-->
<!--<springProfile name="prod">-->
<!--<root level="info">-->
<!--<appender-ref ref="CONSOLE" />-->
<!--<appender-ref ref="DEBUG_FILE" />-->
<!--<appender-ref ref="INFO_FILE" />-->
<!--<appender-ref ref="ERROR_FILE" />-->
<!--<appender-ref ref="WARN_FILE" />-->
<!--</root>-->
<!--</springProfile>-->
</configuration>
注意事项:
注意事项1
. springProfile 配置为dev时, 输出端可以为console, 但配成prod时, 切记关闭console输出.
注意事项2
. 上述配置完成后,启动springboot项目, 发现log.debug()的日志并未输出, 如果想输出debug日志, 则需按如下方式配置:
①.要想输出log.debug() 则首先要把下面的debug,意思是只要是大于等于debug的都输出,但是发现光改一个这个地方还是不会输出,看第二步.
<!--输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
②.然后最下面的因为设置成了info,是给所有整体设置level,也就是说这里设置成info后info以下的都不会输出了,除非单独给某些目录设置成debug,设置成info是因为要是改成debug的话会出来很多debug信息,框架内部的日志,所以要在yml配置文件中指定哪些目录输出级别设为debug,配置方式为如下:
logging:
level:
example.springboot2_logback.scheduler: debug
注意事项3
. SpringBoot默认读取resources下的,若是放到了自定义的路径下, 则需要在application.yml中配置其具体路径(语法: classpath为resources路径)
样例配置如下:
logging:
config: classpath:log/logback-spring.xml
注意事项4
. mybatis的sql日志打印问题?
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,即使我们不配置为info,也不会打印,因为系统默认也是info,所以想要查看sql语句的话,有以下两种操作:
方式一: 把<root level=“info”>改成<root level=“DEBUG”>这样就会打印sql,不过这样日志那边会出现很多其他框架的输出的信息.
方式二: 单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别,如下
level:
example.springboot2_logback.module: debug
这样单独配置后控制台还是以info输出,但是在上面目录我指定为debug了上面目录下的log.debug也会输出
logging:
config: classpath:log/logback-spring.xml
3.3 application.yml 样例
spring:
profiles:
active: dev
3.4 application-dev.yml 样例
server:
address: 11.11.11.11
port: 8080
spring:
profiles:
name: project001
aop:
proxy-target-class: true
auto: true
datasource:
druid:
moduleName:
url: jdbc:mysql://localhost:3306/project001_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 20
myemail:
to-user: xx@yy.com
to-name: BigDataMonitor
host: smtp.provider.com
port: 25
from-user: zz@xx.com
from-password: xxxxx
from-name: This is from Test Program
#logging:
# config: classpath:log/logback-spring.xml
# level:
# root: info
#mybatis:
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.5 application-prod.yml 样例
server:
address: 22.22.22.22
port: 8080
spring:
profiles:
name: project001
aop:
proxy-target-class: true
auto: true
datasource:
druid:
moduleName:
url: jdbc:mysql://{DBMS_IP}:{DBMS_PORT}/project001_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 20
myemail:
to-user: xx@yy.com
to-name: BigDataMonitor
host: smtp.provider.com
port: 25
from-user: zz@xx.com
from-password: xxxxx
from-name: This is from Test Program
#logging:
# config: classpath:log/logback-spring.xml
# level:
# root: info
#mybatis:
# configuration:
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.6 测试程序清单
MyTestScheduler.java:
package com.example.springboot2_logback.scheduler;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MyTestScheduler {
// private static Logger log = LoggerFactory.getLogger(MyTestScheduler.class);
@Scheduled(cron = "*/10 * * * * ?")
public void test(){
log.debug(">>>>>>>>>>>>>>>>>>>>>>>>>> debug");
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>> info");
log.warn(">>>>>>>>>>>>>>>>>>>>>>>>>> warn");
log.error(">>>>>>>>>>>>>>>>>>>>>>>>>> error");
}
}
引用列表: