日志输出指南

在这里插入图片描述

日志输出指南

一、入参和出参

日志应该为统一对入参和出参进行记录,方便线上排查问题
具体实现方式的话有三种方式

  • 通过 HandlerInterceptor 的方式进行拦截
  • 通过 Spring 的切面
  • 通过 Filter

三种效果实际上都差不多了,性能得话 Filter 最高、切面最低、HandlerInterceptor 居中,但是性能也差不了多少哪种习惯用那种
调用链路是 请求先过 Filter -> HandlerInterceptor -> 切面
日志数据存入 ELK

ELK 的 Logstash 需要和运维沟通修改解析

教程一:日志接口级统一出入参实现

二、日志级别

我们主要用的分为四个级别

  • Debug
  • Info
  • Warn
  • Error

如果级别指定的够清晰的话,那么我们是可以通过 logback.xml 在线上对不需要的日志进行排查,让其不打印且不输出到控制台的

2.1、Debug 级别的定义和例子

在开发、调试和测试阶段的时候使用的消息级别,正式环境不打印。包括参数信息、调试细节、返回值信息等等。

例如:

log.debug("[正常请求]{}", uniqueId);

2.2、Info 级别的定义和例子

Info 日志主要记录系统关键信息,旨在保留系统正常工作期间关键运行指标。

例如:

1

这个就是启动日志里面 Sentinel 输出的日志
就比如说我们在程序的运行过程中,有一个类 A 里面有一个参数 Type 我们通过启动的时候,通过配置文件去指定不同的 Type 值来区分 A 类的执行流程,这种情况下,在项目启动时就应该将 TYPE 的值给输出出来,因为我有可能没那么方便去看正式环境的 Nacos 中的配置

2.3、Warn 级别的定义和例子

Warn 级别的主要输出警告性质的内容,这些内容是可以预知且是有规划的。

例如:

log.warn("请求的 username 格式不正确{}", username);

比如说这种判断格式是否正确的,就可以用 warn 来进行输出,主要是可以提前预知,比如说参数为 null 等

2.4、Error 级别的定义和例子

Error 级别主要针对于一些不可预知的信息,诸如:错误、系统异常等。

例如:

try{
	//业务代码
} catch (Exception ex) {
    log.error("系统异常 = ",ex);
}

比如说在调用远程服务的时候,就可能出现我们并不清楚的异常,比如说网络问题,比如说业务逻辑问题等等

error 和 warn 的区别可以简单理解为,warn 会去输出我们自己定义的业务异常,而 error 更多的情况是非我们自己手动抛出的系统异常

三、日志规范

3.1、不要输出无意义的日志

尽量在输出日志的时候,将一定的关键参数也带上,不然即使是测试时你知道这里出现异常,但是也还得去进行调试才能定位到问题的所在。

反例:

log.error("xxx 类 xxx 接口 报错!!!");

正例:

log.error("xxx 类 xxx 接口 报错, msgId:{}",id);

就比如说反例的例子,我确实看到了有报错,但是我并不清楚为什么报错,我还是得通过大量的排查来解决。

3.2、使用占位符而不是拼接

这个主要是性能问题,占位符的性能远大于字符串拼接

反例:

log.error("xxx 类 xxx 接口 报错, msgId:" + id);

正例:

log.error("xxx 类 xxx 接口 报错, msgId:{}",id);

3.3、尽量使用日志级别开关

这个还是性能问题

反例:

log.debug("xxx 类 xxx 接口 报错, user:{}" , JSON.toJSONString(user));

正例:

if (log.isDebugEnabled()) {
	log.debug("xxx 类 xxx 接口 报错, user:{}" , JSON.toJSONString(user));
}

这里的问题主要在于

2

虽然 Logback 在代码中对日志级别进行了判断,如果不满足级别则直接 return 但是占位符这个时候实际上可能已经替换好了
这样就会浪费性能

就比如说上面的例子,在传入到替换之前的时候已经提前做了 JSON.toJSONString(user) 的转换,然后进入之后判断不满足日志输出级别,然后 return 出去,但是这个时候,无论有没有输出日志,实际上你已经转换了,性能已经损耗了

但是如果你使用

log.error("xxx 类 xxx 接口 报错, msgId:{}",id);

这种方式的话,就可以不用提前判断了,因为他不涉及到转换的问题,会在判断结束之后再去调用 toString() 方法进行占位符的替换。

当然如果你用很神奇的写法,那就得提前判断一下了
比如说:
log.debug.(String.format ("xxx 类 xxx 接口 报错, msgId:{}" , id ));

3.4、别使用系列

不要用这些输出

  • System.out
  • System.err
  • e.printStackTrace()

别问,问就是懂的都懂

3.5、输出日志时尽量不要使用转换工具

比如说如果你使用了 JSON 工具来进行转换,一个是性能低,还有一个问题是如果这个类的 get 方法被重写了,那么就会导致异常的产生

因为在日志输出的时候会去调用对象的 toString() 进行输出,所以基本上是不需要进行转换输出的

写法为:

log.debug("xxx 类 xxx 接口 报错, user:{}" , user);

不需要你手动去调用 toString() ,如果手动调用的话,会在日志运行级别判断之前就损耗了性能,参照 3.3 条的问题
如果有需求非要转换输出,那么也参照 3.3 条的正例

3.6、e.getMessage() 和 e 的区别

在输出系统异常时,应该采用 e 来进行输出

比如说:

log.error("系统异常 = ",e);

e 是可以省略占位符的,如果你采用占位符的话,那么等同于 e.toString
具体情况参照 3.8 条

在输出自定义异常时,只需要采用 e.getMessage()

比如说:

log.warn("用户名或密码错误 : {}", e.getMessage());

这里的主要问题是如果是系统异常的话,我们是需要知道堆栈信息,方便去看异常出现处的,但是如果是自定义的业务异常的话,很多时候我们对他的堆栈并不感兴趣。

3.7、尽量不要重复打印日志

反例:

log.warn("用户名或密码错误 : {}", e.getMessage());
log.warn("该用户的用户名为 : {}", user.getUserName());
log.warn("该用户的密码为 : {}", user.getPasswrod());

这些日志应该被封装为一个

正例:

log.warn("用户名或密码错误 用户名: {}, 密码:{}, 异常信息:{}", user.getUserName() , user.getPasswrod() , e.getMessage());

3.8、输出 Throwable 时,可以不使用占位符的几种情况

3

可以看到只有两种情况是可以不使用占位符的,其他情况是需要使用的

3.9、对日志信息进行脱敏处理

后面想起来了再更新这块

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值