目录
SpringBoot 日志
日志概述
为什么要学日志
⽇志对我们来说并不陌⽣, 从JavaSE部分, 我们就在使⽤ System.out.print 来打印⽇志了. 通过打印⽇志来发现和定位问题 , 或者根据⽇志来分析程序的运⾏过程. 在Spring的学习中, 也经常根据控制台的⽇志来分析和定位问题.
随着项⽬的复杂度提升, 我们对⽇志的打印也有了更⾼的需求, ⽽不仅仅是定位排查问题.
⽐如需要记录⼀些⽤⼾的操作记录(⼀些审计公司会要求), 也可能需要使⽤⽇志来记录⽤⼾的⼀些喜好,把⽇志持久化, 后续进⾏数据分析等. 但是 System.out.print
不能很好的满⾜我们的需求, 我们就需要使⽤⼀些专⻔⽇志框架(专业的事情交给专业的⼈去做)
⽇志的⽤途
通过前⾯的学习, 我们知道⽇志主要是为了发现问题, 分析问题, 定位问题的, 但除此之外, ⽇志还有很多⽤途
- 系统监控
监控现在⼏乎是⼀个成熟系统的标配, 我们可以通过⽇志记录这个系统的运⾏状态, 每⼀个⽅法的响应时间, 响应状态等, 对数据进⾏分析, 设置不同的规则, 超过阈值时进⾏报警. ⽐如统计⽇志中关键字的数量,并在关键字数量达到⼀定条件时报警,这也是⽇志的常⻅需求之⼀
- 数据采集
数据采集是⼀个⽐较⼤的范围, 采集的数据可以作⽤在很多⽅⾯, ⽐如数据统计, 推荐排序等.
- 数据统计: 统计⻚⾯的浏览量(PV), 访客量(UV), 点击量等, 根据这些数据进⾏数据分析, 优化公司运营策略
- 推荐排序: ⽬前推荐排序应⽤在各个领域, 我们经常接触的各⾏各业很多也都涉及推荐排序, ⽐如购物, ⼴告, 新闻等领域. 数据采集是推荐排序⼯作中必须做的⼀环, 系统通过⽇志记录⽤⼾的浏览历史, 停留时⻓等, 算法⼈员通过分析这些数据, 训练模型, 给⽤⼾做推荐.下图中的数据源, 其中⼀部分就来⾃于⽇志记录的数据.
- ⽇志审计
随着互联⽹的发展,众多企业的关键业务越来越多的运⾏于⽹络之上. ⽹络安全越来越受到⼤家的关注, 系统安全也成为了项⽬中的⼀个重要环节, 安全审计也是系统中⾮常重要的部分. 国家的政策法规、⾏业标准等都明确对⽇志审计提出了要求. 通过系统⽇志分析,可以判断⼀些⾮法攻击, ⾮法调⽤,以及系统处理过程中的安全隐患
⽐如, ⼤家平时都在做运营系统, 其中运营⼈员在通过界⾯处理⼀些数据的时候, 如果没有清楚的⽇志操作记录, ⼀条数据被删除或者修改, 你是⽆法找到是谁操作的,但是如果你做了相应的记录,该数据被谁删除或者修改就会⼀⽬了然.
还有⼀些内部的违规和信息泄漏(⽐如客⼾信息被卖掉)现象出现后, 如果未记录留存⽇志, 为事后调查提供依据, 则事后很难追查(⼀些公司查看客⼾的信息都会被记录⽇志, 如果频繁查询也会报警)
日志使用
日志在企业中的实践:
- 对日志进行分类
- 借助消息队列,比如kafka,mq等
就是阻塞队列:生产者消费者模型
Spring Boot 项⽬在启动的时候默认就有⽇志输出,如下图所⽰:
它打印的⽇志和 System.out.print
有什么不同呢?
java
@RestController
public class LoggerController {
@RequestMapping("/logger")
public String logger(){
System.out.println("打印⽇志");
return "打印⽇志";
}
}
观察⽇志输出
可以看到, 我们通过 System.out.print
打印的⽇志, ⽐ SpringBoot 打印的⽇志缺少了很多信息.
SpringBoot 内置了⽇志框架 Slf4j
, 我们可以直接在程序中调⽤ Slf4j
来输出⽇志
打印日志
打印⽇志的步骤:
- 在程序中得到**⽇志对象**.
- 使⽤⽇志对象输出要打印的内容
在程序中得到⽇志对象
在程序中获取⽇志对象需要使⽤⽇志⼯⼚ LoggerFactory
,如下代码所⽰:
java
private static Logger logger = LoggerFactory.getLogger(LoggerController.class);
LoggerFactory.getLogger 需要传递⼀个参数, 标识这个⽇志的名称. 这样可以更清晰的知道是哪个类输出的⽇志. 当有问题时, 可以更⽅便直观的定位到问题类
注意:Logger 对象是属于 org.slf4j 包下的, 不要导⼊错包
使用日志对象打印日志
⽇志对象的打印⽅法有很多种,我们可以先使⽤ info() ⽅法来输出⽇志,如下代码所⽰:
- 定义日志对象
- 打印日志
java
@RestController
public class LoggerController {
//日志对象
private static Logger logger= LoggerFactory.getLogger(LoggerController.class);
@PostConstruct
public void print(){
System.out.println("打印日志");
logger.info("--------------要输出日志的内容----------------");
}
}
打印⽇志效果展⽰:
工厂模式:
以前创建对象都是new什么的,工厂模式就是通过一个类,比如上面的
LoggerFactory
,帮助我们创建对象
⽇志框架介绍(了解)
SLF4J不同于其他⽇志框架, 它不是⼀个真正的⽇志实现,而是一个日志门面,⽽是⼀个抽象层, 对⽇志框架制定的⼀种规范,标准, 接⼝. 所有SLF4J并不能独⽴使⽤, 需要和具体的⽇志框架配合使⽤.
具体实现是log4j1/2,logback等等
⻔⾯模式(外观模式)
Facade Pattern
SLF4J是⻔⾯模式的典型应⽤(但不仅仅使⽤了⻔⾯模式).
⻔⾯模式定义
⻔⾯模式 (Facade Pattern)⼜称为外观模式 (其实一般说外观模式更常见,只是这里经常说一个日志门面所以称门面模式),提供了⼀个统⼀的接⼝ , ⽤来访问**⼦系统中的⼀群接⼝** .其主要特征是定义了⼀个⾼层接⼝, 让⼦系统更容易使⽤。目的其实就是为了解耦合
原⽂: Provide a unified interface to a set of interfaces in a subsystem.Facade defines a higher level interface that makes the subsystem easier to use.
解释:要求⼀个⼦系统的外部与其内部的通信必须通过⼀个统⼀的对象进⾏. ⻔⾯模式提供⼀个⾼层次的接⼝,使得⼦系统更易于使⽤.
⻔⾯模式主要包含2种⻆⾊:
外观⻆⾊ (Facade): 也称**⻔⾯⻆⾊,系统对外的统⼀接⼝.**
⼦系统⻆⾊ (SubSystem): 可以同时有⼀个或多个 SubSystem. 每个 SubSytem 都不是⼀个单独的类,⽽是⼀个类的集合. SubSystem 并不知道 Facade 的存在, 对于 SubSystem ⽽⾔, Facade 只是另⼀个客⼾端⽽已(即 Facade 对 SubSystem 透明)
⽐如去医院看病,可能要去挂号, ⻔诊, 化验, 取药, 让患者或患者家属觉得很复杂, 如果有提供接待⼈员, 只让接待⼈员来处理, 就很⽅便.
⻔⾯模式的实现
场景: 回家, 我们会开各个屋的灯. 离开家时, 会关闭各个屋的灯
如果家⾥设置⼀个总开关, 来控制整个屋的灯就会很⽅便.
我们使⽤⻔⾯模式的实现
java
public class FacadePatternDemo {
public static void main(String[] args) {
LightFacade lightFacade = new LightFacade();
lightFacade.lightOn();
lightFacade.lightOff();
}
}
/**
* 灯的⻔⾯
*/
class LightFacade {
private Light livingRoomLight = new LivingRoomLight();
private Light hallLight = new HallLight();
private Light diningLight = new DiningLight();
public void lightOn() {
livingRoomLight.on();
hallLight.on();
diningLight.on();
}
public void lightOff() {
livingRoomLight.off();
hallLight.off();
diningLight.off();
}
}
interface Light {
void on();
void off();
}
/**
* 客厅灯
*/
class LivingRoomLight implements Light {
@Override
public void on() {
System.out.println("打开客厅灯");
}
@Override
public void off() {
System.out.println("关闭客厅灯");
}
}
/**
* ⾛廊灯
*/
class HallLight implements Light {
@Override
public void on() {
System.out.println("打开⾛廊灯");
}
@Override
public void off() {
System.out.println("关闭⾛廊灯");
}
}
/**
* 餐厅灯
*/
class DiningLight implements Light {
@Override
public void on() {
System.out.println("打开餐厅灯");
}
@Override
public void off() {
System.out.println("关闭餐厅灯");
}
}
⻔⾯模式的优点
- 减少了系统的相互依赖. 实现了客⼾端与⼦系统的耦合关系, 这使得⼦系统的变化不会影响到调⽤它的客⼾端;
- 提⾼了灵活性, 简化了客⼾端对⼦系统的使⽤难度, 客⼾端⽆需关⼼⼦系统的具体实现⽅式, ⽽只需要和⻔⾯对象交互即可.
- 提⾼了安全性. 可以灵活设定访问权限, 不在⻔⾯对象中开通⽅法, 就⽆法访问
SLF4J 框架介绍
SLF4J 其实不是真实的日志框架实现,就是其他⽇志框架的⻔⾯ . SLF4J 可以理解为是提供⽇志服务的统⼀API接⼝, 并不涉及到具体的⽇志逻辑实现.
不引⼊⽇志⻔⾯
常⻅的⽇志框架有log4J, logback等. 如果**⼀个项⽬已经使⽤了log4j** ,⽽你依赖的另⼀个类库,假如是Apache Active MQ, 它依赖于另外⼀个⽇志框架logback, 那么你就需要把logback也加载进去.
存在问题:
-
不同⽇志框架的API接⼝和配置⽂件不同, 如果多个⽇志框架共存, 那么不得不维护多套配置⽂件(这个配置⽂件是指⽤⼾⾃定义的配置⽂件).
-
如果要更换⽇志框架, 应⽤程序将不得不修改代码 , 并且修改过程中可能会存在⼀些代码冲突.
-
如果引⼊的第三⽅框架, 使⽤了多套, 那就不得不维护多套配置.
引⼊⽇志⻔⾯
引⼊⻔⾯⽇志框架 之后, 应⽤程序和⽇志框架(框架的具体实现)之间有了统⼀的API接⼝ (⻔⾯⽇志框架实现), 此时应⽤程序只需要维护⼀套⽇志⽂件配置, 且当底层实现框架改变时, 也不需要更改应⽤程序代码.
SLF4J 就是这个⽇志⻔⾯.
总的来说,SLF4J使你的代码独⽴于任意⼀个特定的⽇志API,这是⼀个对于开发API的开发者很好的思想.
日志格式的说明
打印的⽇志分别代表什么呢?
从上图可以看到,⽇志输出内容元素具体如下:
- 时间⽇期:精确到毫秒
- ⽇志级别:ERROR, WARN, INFO, DEBUG 或TRACE
- 进程ID
- 线程名
- Logger名(通常使⽤源代码的类名)
- ⽇志内容
⽇志级别
⽇志级别代表着⽇志信息对应问题的严重性 , 为了更快的筛选符合⽬标的⽇志信息.
试想⼀下这样的场景,假设你是⼀家 2 万⼈公司的⽼板, 如果每个员⼯的⽇常⼯作和琐碎的信息都要反馈给你, 那你⼀定⽆暇顾及. 于是就有了组织架构,⽽组织架构就会分级,有很多的级别设置,如下图所⽰:
有了组织架构之后,就可以逐级别汇报消息了, 例如:组员汇报给组⻓, 组⻓汇报给研发⼀组, 研发⼀组汇报给 Java 研发, 等等依次进⾏汇报.
⽇志级别⼤概是同样的道理,有了⽇志级别之后就可以过滤⾃⼰想看到的信息了, ⽐如只关注error级别的, 就可以根据级别过滤出来error级别的⽇志信息, 节约开发者的信息筛选时间.
日志级别的分类
⽇志的级别从⾼到低依次为: FATAL、ERROR、WARN、INFO、DEBUG、TRACE
- FATAL: 致命 信息,表⽰需要⽴即被处理的系统级错误.
- ERROR: 错误 信息, 级别较⾼的错误⽇志信息, 但仍然不影响系统的继续运⾏.(不能长期不解决)
- WARN: 警告 信息, 不影响使⽤, 但需要注意的问题
- INFO: 普通信息, ⽤于记录应⽤程序正常运⾏时的⼀些信息, 例如系统启动完成、请求处理完成等.
- DEBUG: 调试信息, 需要调试时候的关键信息打印.
- TRACE: 追踪信息, ⽐DEBUG更细粒度的信息事件(除⾮有特殊⽤意,否则请使⽤DEBUG级别替代)
⽇志级别通常和测试⼈员的Bug级别没有关系.
⽇志级别是开发⼈员设置的, ⽤来给开发⼈员看的 . ⽇志级别的正确设置, 也与开发⼈员的⼯作经验有关. 如果开发⼈员把error级别的⽇志设置成了info, 就很有可能会影响开发⼈员对项⽬运⾏情况的判断. 出现error级别的⽇志信息较多时, 可能也没有任何问题. 测试的bug级别更多是依据现象和影响范围来判断
⽇志级别的顺序:
级别越⾼, 收到的消息越少
日志级别的使用
⽇志级别是开发⼈员⾃⼰设置的. 开发⼈员根据⾃⼰的理解来判断该信息的重要程度
类似公司管理, 通常由领导来判断什么样的事情需要汇报, 什么样的事情不需要汇报
针对这些级别, Logger 对象分别提供了对应的⽅法, 来输出⽇志.
java
/**
* 打印不同级别的⽇志
* @return
*/
@RequestMapping("/printLog")
public String printLog() {
logger.trace("================= trace ===============");
logger.debug("================= debug ===============");
logger.info("================= info ===============");
logger.warn("================= warn ===============");
logger.error("================= error ===============");
return "打印不同级别的⽇志" ;
}
SpringBoot 默认的⽇志框架是Logback, Logback没有 FATAL 级别, 它被映射到 ERROR .
出现fatal⽇志,表⽰服务已经出现了某种程度的不可⽤, 需要需要系统管理员紧急介⼊处理. 通常情况下, ⼀个进程⽣命周期中应该最多只有⼀次FATAL记录.
观察打印的⽇志结果:
结果发现, 只打印了info, warn和error级别的⽇志
这与⽇志级别的配置有关 , Spring⽇志的输出级别默认是 info级别, 所以只会打印⼤于等于此级别的⽇志, 也就是info, warn和error.
但可以改,在 application.yml 文件中可以配置
yamllogging: level: root: debug
改成这样那些debug及其以上的日志就能打印出来了,但会变得很多
⽇志配置
上述是⽇志的使⽤, ⽇志框架⽀持我们更灵活的输出⽇志, 包括内容, 格式等.
配置⽇志级别
⽇志级别配置只需要在配置⽂件中设置"logging.level"配置项即可,如下所⽰:
properties 和yml只需要配置其中⼀个即可.
⼆者转换⽅式: properties⽂件的点( . ) 对应yml⽂件中的换⾏
以下两个配置, 根据项⽬使⽤其中之⼀.
properties配置
properties
logging.level.root: debug
yml配置
yaml
logging:
level:
root: debug
重新运⾏上述代码, 观察结果:
也可以分目录去打印日志
yaml
logging:
level:
root: info
com:
Hsu:
demo: debug
这一段的意思就是Spring的日志只打印info级别以上的,而我们的项目日志打印debug级别以上的
⽇志持久化
长期保存。存储在外存硬盘中。因为内存一重启就刷新了
以上的⽇志都是输出在控制台上的, 然⽽在线上环境中, 我们需要把⽇志保存下来, 以便出现问题之后追溯问题. 把⽇志保存下来就叫持久化.
⽇志持久化有两种⽅式
- 配置⽇志⽂件名
- 配置⽇志的存储⽬录
配置⽇志⽂件的路径和⽂件名:
properties配置
properties
logging.file.name: logger/springboot.log
yml配置
yaml
# 设置⽇志⽂件的⽂件名
logging:
file:
name: logger/springboot.log
后⾯可以跟绝对路径或者相对路径
如果只是有文件名,那么就存放在工程目录下
配置⽇志⽂件的保存路径
properties配置
properties
logging.file.path: D:/temp
yml配置
yaml
# 设置⽇志⽂件的⽂件名
logging:
file:
path: D:/temp
这种⽅式只能设置⽇志的路径, ⽂件名为固定的spring.log
运⾏程序, 该路径下多出⼀个⽇志⽂件: spring.log
注意:
logging.file.name
和logging.file.path
两个都配置的情况下, 只⽣效其⼀ , 以logging.file.name
为准前者能设置名字也能设置路径,因此用前者就好
配置⽇志⽂件分割
**原因:**如果我们的⽇志都放在⼀个⽂件中, 随着项⽬的运⾏, ⽇志⽂件会越来越⼤, 需要对⽇志⽂件进⾏分割.
当然, ⽇志框架也帮我们考虑到了这⼀点, 所以如果不进⾏配置, 就⾛⾃动配置
默认⽇志⽂件超过10M就进⾏分割
配置项 | 说明 | 默认值 |
---|---|---|
logging.logback.rollingpolicy.file-name-pattern |
⽇志分割后的⽂件名格式 | ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz |
logging.logback.rollingpolicy.max-file-size |
⽇志⽂件超过这个⼤⼩就⾃动分割 | 10MB |
配置⽇志⽂件分割:
properties配置
properties
logging.logback.rollingpolicy.file-name-pattern=${LOG_FILE}.%d{yyyy-MM-dd}.%i
logging.logback.rollingpolicy.max-file-size=1KB
yml配置
yaml
logging:
logback:
rollingpolicy:
max-file-size: 1KB
file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i
⽇志⽂件超过1KB就分割 (设置1KB是为了更好展⽰. 企业开发通常设置为200M, 500M等, 此处没有明确标准)
分割后的⽇志⽂件名为: ⽇志名.⽇期.索引
不是达到1KB就分割的,而是达到后以行来分割,而不是以字符
项⽬运⾏, 多打印⼀些⽇志, ⽇志分割结果:
配置⽇志格式
⽬前⽇志打印的格式是默认的
打印⽇志的格式, 也是⽀持配置的. ⽀持控制台和⽇志⽂件分别设置
配置项 | 说明 | 默认值 |
---|---|---|
logging.pattern.console |
控制台日志格式 | %clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}}){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} |
logging.pattern.file |
日志文件得到日志格式 | %d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ${LOG_LEVEL_PATTERN:-%5p} ${PID:- } --- [%t] %-40.40logger{39} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx} |
配置项说明:
- %clr(表达式){颜⾊} 设置输⼊⽇志的颜⾊
⽀持颜⾊有以下⼏种:
- blue
- cyan
- faint
- green
- magenta
- red
- yellow
- %d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}} ⽇期和时间--精确到毫秒
%d{} ⽇期
${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd'T'HH:mm:ss.SSSXXX}
⾮空表达式, 获取系统属性LOG_DATEFORMAT_PATTERN
, 若属性LOG_DATEFORMAT_PATTERN
不存在,则使⽤-yyyy-MM-dd HH:mm:ss.SSSXXX
格式, 系统属性可以System.getProperty("LOG_DATEFORMAT_PATTERN")
获取
-
%5p
显⽰⽇志级别ERROR,MARN,INFO,DEBUG,TRACE. -
%t
线程名.%c
类的全限定名.%M
method.%L
为⾏号.%thread
线程名称.%m
或者%msg
显⽰输出消息.%n
换⾏符 -
%5
若字符⻓度⼩于5,则右边⽤空格填充.%-5
若字符⻓度⼩于5,则左边⽤空格填充.%.15
若符⻓度超过15,截去多余字符.%15.15
若字符⻓度⼩于15,则右边⽤空格填充. 若字符⻓度超过15,截去多余字符
更多说明, 参考: https://logback.qos.ch/manual/layouts.html#conversionWord
日志可以设置颜色,但只能设置控制台的颜色。因为日志可以出现在控制台、文件、消息队列。因为这不是日志的支持,而是idea的支持
设置了颜⾊, 却没有⽣效?
需要配置, 让idea⽀持控制台颜⾊显⽰
启动类设置
DemoApplication
打开启动配置, 添加VM options
- 添加VM options
-Dspring.output.ansi.enabled=ALWAYS
- 重新启动程序, 就发现控制台⽀持颜⾊了
properties配置
properties
logging.pattern.console='%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
yml配置
yaml
logging:
pattern:
console: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
file: '%d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
项⽬运⾏, 观察⽇志变化:
通常情况下, 咱们就使⽤默认的⽇志格式打印即可.
更简单的⽇志输出
每次都使⽤ LoggerFactory.getLogger(xxx.class)
很繁琐, 且每个类都添加⼀遍, lombok 给我们提供了⼀种更简单的⽅式.
- 添加 lombok 框架⽀持
- 使⽤
@slf4j
注解输出⽇志。
添加 lombok 依赖
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
输出⽇志
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class LogController {
public void log(){
log.info("--------------要输出⽇志的内容----------------");
}
}
lombok 提供的 @Slf4j
会帮我们提供⼀个⽇志对象 log
, 我们直接使⽤就可以.
总结
-
⽇志是程序中的重要组成部分,使⽤⽇志可以快速的发现和定位问题,Spring Boot 内容了⽇志框架,默认情况下使⽤的是 info ⽇志级别将⽇志输出到控制台的,我们可以通过 lombok 提供的
@Slf4j
注解和log
对象快速的打印⾃定义⽇志. -
⽇志包含 6 个级别, ⽇志级别越⾼,收到的⽇志信息也就越少,我们可以通过配置⽇志的保存名称或保存⽬录来将⽇志持久化.