个人小结的Java中常见日志与异常处理实践

原本我以为日志和异常没啥东西,只要按照《Effective Java》中编码就行。但是实际情况告诉我写Java代码的人有些不知道《Effective Java》,还有些人只会依样画葫芦,甚至把错误的拼写比如accessable也一并画了下来。更让我头疼的是,两者加在一起导致的前人所有的异常都打印ERROR级别日志+抛出异常,后面的人想也不想一样的也全打ERROR级别日志+抛出异常。好吧,其实我想说打印日志也是有好的实践的,Java的异常也不是随便乱用的。

不是所有异常都需要打印ERROR级别日志

这很明显,但是似乎有人不明白什么时候不打ERROR级别日志。以下是个人建议的级别使用策略:

级别 使用策略
DEBUG 顾名思义,调试级别的信息。详细的参数信息,组件运行状态等等乱七八糟的都行,方便你调试程序。从另一个角度来讲,INFO不能显示的信息DEBUG都可以显示。
INFO 需要关注的信息,比如删除文件时。这里不用DEBUG级别的原因是删除文件一般认为是需要关注的。而且很多生产环境日志阈值是INFO。
WARN 用户输入引起的异常,可恢复的异常。从另一个角度来说,不是系统严重的问题(比如数据库宕机了)都可以用WARN级别。
ERROR 不可恢复的错误

针对滥用ERROR级别的问题,首要的是分清哪些是用户引起的,哪些是可恢复的,哪些是不可恢复的,系统自身错误引起的。比如用户输入格式错误,这明显就不能使用ERROR级别。远程服务器错误一般就用ERROR级别,但是假如你设计了类似双机备份,集群环境,重试机制等等单次远程服务器错误就不一定是不可恢复的,这时你使用WARN也没啥问题。

捕获异常时该怎么做?

打日志?包装后再抛出?两个都做?其实都没错,只是应用场景不一样。

处理策略 场景
仅打印日志 当前逻辑就能处理掉的,不需要上层再处理的,或者本身就是最上层。属于吃掉异常的策略,比如服务错误时界面的处理策略
再抛出异常 不管是包装还是直接抛出,常用的处理策略,包装的话可能属于”异常翻译“,使用当前层的语义描述异常的原因
打印日志又抛出异常 个人比较怀疑这种处理方式,不过对某些”异常翻译“有问题的场景可以使用,比如明明是系统错误,你却说是用户输入错误。维护时你可能需要在转义的地方打印日志来方便排查

一般来说,前两种是配套使用的。以fault barrier处理策略(即使用异常驱动逻辑)为例,顶层负责打印日志,用户输入异常打印WARN级别日志,运行时错误使用ERROR级别打印日志,下层所有服务处理过程中出现异常包括或者直接往上抛,快速失败。

顶层

下层

Java的Exception和fault barrier的使用

个人一直认为Java的Exception是个非常好的东西,用的好的话,使用异常驱动的设计以及快速失败的优势很明显,代码很清晰,所有异常分支只要另外考虑就行,基于返回值的异常分支需要额外的判断和处理逻辑,和正常逻辑混在一起难以分辨。
Java的Exception分为异常和错误,一般认为异常是可以恢复的,个人认为就是外部引起的,比如用户输入,相对的,错误是不可恢复的,个人认为是由内部引起的,比如数据库。大家熟知的Spring做了一件事情,就是把类似ORM的“错误”转换为了异常,某种程度上是Exception的退化,但是个人认为这是设计风格上的变化,理念与具体实现交给开发者来决定。
fault barrier个人认为是鼓励开发者考虑主流程,将异常交给其他代码来负责,使得逻辑更加清晰。其实实际编码就按照Java的Exception方法在异常分支处抛出异常,交给上层负责即可。

最后,上面这些是我个人对于日志和异常的理解,如有建议,欢迎提出。