日志是应用运行中被动或者主动输出,记录了应用相关信息的工具。
日志一般有多个日志级别,包括TRACE、DEBUG、WARN、INFO、ERROR、FATAL。
其中TRACE,DEBUG一般生产环境不输出,INFO属于一般信息,用于记录应用相关参数,运行状态等,WARN级别属于轻微的警告,用于一些不应该出现的或者轻微的错误,不影响应用。而ERROR和FATAL就是我们所说的异常了,这种级别的日志用于记录会影响业务进行和程序运行的情况。
合理的输出异常本身对于应用的维护性有着很大的帮助。
异常的类别
要输出合理的异常信息,首先要明确有哪些异常,在我看来,异常无非有三种
程序异常
程序异常就是我们所说的真的挂了,也就是编程语言或者其运行环境自身提示异常,比如空指针异常。这种异常发生应用就已经出错的。这种错误不应该是开发人员手动输出的,而一般由公共的Exception Handler处理。这种异常理论上不应该直接输出,而是应该通过参数检查来避免,当检查不通过时应该转为输出可读的异常信息
调用下游依赖异常
现在巨型应用慢慢开始拆分,就出现了上下游调用的情况。比如一个财务系统,可能涉及到一些人员的信息展示,而这些信息可能由另外一个专门的人员服务来负责。那么当调用下游时就有可能出错,可能是返回不正确(null)也可能是超时,特别是互联网公司,很多超时都是200毫秒,网络抖动就会报错。
业务报错
对于任何输入都应该有验证,如果传入了错误的参数或者缺乏必要的参数,这种是逻辑上需要预处理的,而且技术上这种异常不是来自正常请求,因为调用方应该提供正确参数,若有问题应该在测试阶段暴露。
还有一种报错来自应用框架。现代应用开发很少从底层弄上来,一般会使用一些框架,比如Spring Franework。这种框架自身会输出两种日志,一种是启动日志,一种是异常日志。
比如Spring MVC在http请求缺乏必要参数时会抛出异常,这种异常大部分应该被开发人员手动转为有意义的异常。当然这个要看情况。
异常日志的意义
异常本身就代表了应用没有正常运行了,而异常日志的意义我个人觉得有几类:
需要代码修复
这种主要针对第一种异常,代码真的报错了,不管是什么原因,都代表这种情况可能出现,那么这里就需要人员去处理,处理之后重新上线。
如果测试是充分的,那么这种异常一般是异常的调用或者异常的数据产生(异常不代表数据是错的,这里主要强调非正常数据,数量不大),那么这种问题尽快修复就行了。
需要沟通
这种主要是针对第二种异常,下游依赖出现问题的时候,那么首先要通知下游的服务提供方,它们可能有自己的监控,也可能没有,也可能这种异常本身没法被提供方的监控所感知,不管怎样,首先要让相关人员知晓情况,然后再说评估影响,作出数据修复或者继续观察。
需要知晓
有些异常可能只是让人心里有数,比如不正确的外部参数,这种如果自己的应用没有问题,那么一般都是不正常的请求来源,不需要提供正确的响应,不如说这种情况下的异常才是正确行为。
但是这种情况都需要相关人员知晓,如果异常进一步扩大或者极速增加,那么还是需要采取一些防御手段的。
合理的异常日志内容
异常日志的内容要有合理有意义,主要是能够良好支撑我们对于日志的期望。日志框架的选择和收集方式我们这里不谈,主要说说日志的内容。
互联网公司提供产品和服务给用户,通常这种服务是全天候的,根据业务的情况还可能出现非工作日业务量更大的情况,那么就需要有团队成员值班。而一个人应该了解自己团队所负责的服务和业务,但是对于每一个模块,每一行代码不可能都非常熟悉,那么异常日志就是提供一个快速判断,进而作出正确决策的输入来源。
首先完整的堆栈信息是必要的,特别是对于程序异常,光跑出一个NullException但是没有具体的代码行数是没有意义的,完整的堆栈可以快速定位,这种情况的保证一般是框架自带的,偶尔也有手动输出的,只要不忘记传入exception就行了。
logger.error("计算配送单{}时长失败", id, e);
如果错误是框架统一异常处理的,一般会输出堆栈信息,比如Spring的DefaultErrorWebExceptionHandler。
对于下游调用异常的情况,首先为了方便沟通,需要输出的是调用方的信息,比如
logger.error("调用人员服务超时,当前订单{}", id, …
