在每个项目中,日志的地位都是举足轻重的,好的日志可以大大提升问题确认的速度,尤其是针对无法断点调试的线上环境。所以好的日志规范是非常必要的。
日志级别
首先不得不说的就是日志的级别,级别的划分主要是为了区分不同目的、不同重要程度的信息,以方便对日志进行过滤。
日志级别由高到底为ERROR
>WARN
>INFO
>DEBUG
>TRACE
。
ERROR
级别的日志用来标注一些影响到当前程序的运行,或者当前接口、请求的运行的异常情况,常见的有RuntimeException
、第三方接口返回的异常状态码等。WARN
级别的日志用来标注一些暂时还没有影响程序或请求的运行,但是其本质上是异常的,比如说,代码中尝试打开一个不存在的文件,但是我们做了默认文件的处理不会抛出异常。当我们需要知道,这个文件到底是不是存在的时候,就通过WARN
级别的日志输出。INFO
级别的日志用来标注程序运行的一些必要信息,比如业务的数据交换,逻辑步骤等,可以方便开发人员知道程序的运行状态。DEBUG
级别的日志用来标注和业务相关的更多更详细的信息,比如,每一步的数据变化、调用情况。TRACE
级别的日志是用来标注系统或者程序底层的状态信息,不会用来标注业务相关的信息,所以一般程序中也是见不到这个级别的日志的。
使用规范
先从日志的实现上来说,常见的日志门面框架就是slf4j
和commons-logging
。他们的区别和优缺点就不专门的去说了,肯定是各有所长,普遍使用slf4j
。而slf4j
的实现,一开始是log4j
,后来对其进行了升级优化,就产生了LogBack
,如果你使用SpringBoot的话,那么什么都不用做,默认就是它。
日志格式
在输出日志的时候,当需要在其中加入变量的时候,很多人喜欢使用字符串拼接的方式,如:
log.info("xxx业务的请求参数有 :id=" + id + ",name=" + name);
这样使用,会产生多余的字符串对象,占用空间,影响性能。
正确的使用姿势应该是使用字符串格式化操作。如:
// []用来做变量的隔离,方便区分值,{}是占位符,会将后面传入的变量值拼接到该位置
log.info("xxx业务的请求参数有 : id=[{}],name=[{}]", id, name);
提示信息中英文皆可,主要是为了快速定位问题,千万不要因为所谓的使用中文比较low,而在自己英文水平不足够的情况下使用英文。不能定位到问题,什么高端、低级都是扯淡。我的英文比较垃圾,所以我在开发中,基本上使用全中文模式。
各级别日志的使用
ERROR
ERROR级别日志的使用时最简单的也是最容易出问题的。简单在于,这个日志级别在一般开发程序中就2种使用场景,一是捕获到了异常,二是第三方接口中返回错误的状态码。容易出问题在于,很多人在输出异常信息的时候会忽略堆栈信息。
try {
// TODO something
} catch (Exception e) {
// 这种方式,输出的信息只有一个简单的提示,根本无法定位到异常发生在哪里
// 比如说NPE,只会简单输出一个 null,还不如不输出
log.error("xxx业务操作异常:[{}]", e.getMessage());
// 正确的输出方式如下,日志信息中,还可以包括其它任何信息,但是最后一个堆栈信息的输出是一定不能省略的
log.error("xxx业务操作异常:[{}]", e);
}
对于error级别的日志而言,只要在catch时使用,并且正常输出堆栈信息。就不会有什么问题。
WARN
warn级别的日志在一般业务逻辑中使用也是比较少的。它主要针对的是一个异常但并没有影响程序正常运行的情况。例如:
try {
File file = new File(filePath);
if (file.exists()) {
// TODO 读取文件
} else {
// 文件不存在则打开默认文件
file = new File(defaultPath);
// 由于通过if判断了文件是否存在,所以这个程序并不会抛出异常
// 但是对于程序来说,用户传递了一个不存在的文件,这是一个异常情况,没报错是因为我们代码的健壮性好
// 所以,如果此信息有必要输出的话,应该使用warn级别的日志
log.warn("文件[{}]不存在,已打开默认文件:[{}]", filePath, defaultPath);
}
}
INFO
info级别日志用来记录业务状态和数据的变更以及操作步骤的记录。是开发中最常用的日志级别。比如service的出入口、第三方数据接口数据交互,远程调用数据流向等。
public ReturnType serviceMethodName(T t) {
log.info("开始执行xxxx业务,入参:[{}]", t);
// TODO something
log.info("执行xxx业务结束:出参:[{}]", t);
}
提示信息按照自己的习惯写即可,上面也是我临时想的简单的demo,service的出入口并不是一定要有的,比如说service中没有什么数据变化或者没有什么有意义的操作。service中间如果有关键数据交互以及主要业务步骤,也应该增加相应的info级别日志以方便调试。
以下几种情况,笔者建议,一定要使用日志说明:
- 调用第三方接口时的出入参,因为第三方接口的问题你是很难追溯的。即使你已经确认不是你的问题,如果没有日志,对方可能也会推脱说,是你调用出错了。
- 如果你使用SOA或者微服务等分布式架构,那么远程调用的出入参也要明确记录下来,因为分布式的调试也是很麻烦的。
- 使用多线程时,一定要记录参数变化以及当前的线程名,如果你有进行过多线程的调试,那么你一定知道,为什么一定要记录。
- 所有暴露的api接口的出入参(尤其是入参),项目开发中联调的沟通成本很大的原因大部分是因为,前端认为他传参不可能错,后端认为一定是你参数有问题。我一般都选择,直接把接口的入参和出参拿出来,谁的问题一目了然。如果没有日志,那就只能通过抓包,这比较麻烦。所以,笔者建议,一定要把接口的出入参在日志中体现出来。
- 关键的业务逻辑,一定要多打日志,不要怕麻烦,比如电商中的订单,数据分析中的埋点计算。
DEBUG
debug级别是用来输出更详细的相关信息的。首先,要确定的是,线上环境一定不能使用debug级别的日志输出。如果线上需要debug级别的日志调试,应该为其加上开关。
public ReturnType seviceMethodName() {
log.info("some info level log");
// TODO something
if (log.isDebugEnabled()) {
log.debug("some debug level log");
}
}
TRACE
trace用来记录详细的系统运行信息,业务代码中避免使用即可。
来源:CSDN
作者:Lawliet-zhang
链接:https://blog.csdn.net/meiyouyaCSDN/article/details/103645953