如何编写错误日志才更容易排查问题
如何编写错误日志才更容易排查问题
一、系统产生错误的常见原因
1、疏忽导致
疏忽是指程序员能力完全可避免此类错误但实际上没做到。比如将 && 敲成了 & , == 敲成了 = ; 边界错误, 复合逻辑判断错误等。 疏忽要么是程序员注意力不够集中, 比如处于疲倦状态、加班通宵、边开会边写程序; 要么是急着实现功能,没有顾及程序的健壮性等。
改进措施: 使用代码静态分析工具,通过单元测试行覆盖可有效避免此类问题。
2、 错误与异常处理不够周全导致的
比如输入问题。 计算两个数相加, 不仅要考虑计算溢出问题, 还要考虑输入非法的情形。对于前者,可能通过了解、犯错或经验就可以避免, 而对于后者,则必须加以限定,以使之处于我们的智商能够控制的范围内,比如使用正则表达式过滤掉不合法的输入。对于正则表达式必须进行测试。对于不合法输入, 要给出尽可能详细、易懂、友好的提示信息、原因及建议方案。
改进措施: 尽可能周全地考虑各种错误情形和异常处理。在实现主流程之后,增加一个步骤:仔细推敲可能的各种错误和异常,返回合理错误码和错误描述。每个接口或模块都有效处理好自己的错误和异常,可有效避免因场景交互复杂导致的bug. 譬如,一个业务用例由场景A.B.C交互完成。实际执行A.B成功了,C失败了,这时B需要根据C返回合理的代码和消息进行回滚并返回给A合理的代码和消息,A根据B的返回进行回滚,并返回给客户端合理的代码和消息。这是一种分段回滚的机制,要求每个场景都必须考虑异常情况下的回滚。
3、逻辑耦合紧密导致
由于业务逻辑耦合紧密, 随着软件产品一步步发展, 各种逻辑关系错综复杂, 难以看到全局状况, 导致局部修改影响波及到全局范围,造成不可预知的问题。
改进措施: 编写短函数和短方法, 每个函数或方法最好不超过 50 行。 编写无状态函数和方法, 只读全局状态, 相同的前提条件总是会输出相同的结果, 不会依赖外部状态而变更自己的行为; 定义合理的结构、 接口和逻辑段, 使接口之间的交互尽可能正交、低耦合; 对于服务层, 尽可能提供简单、正交的接口; 持续重构, 保持应用模块化和松耦合, 理清逻辑依赖关系。对于有大量业务接口相互影响的情况, 必须整理各个业务接口的逻辑流程及相互依赖关系, 从整体上进行优化; 对于有大量状态的实体, 也需要梳理相关的业务接口, 整理状态之间的转换关系。
4、算法不正确导致
改进措施: 首先将算法从应用中分离出来。 若算法有多种实现, 可以通过交叉校验的单元测试找出来, 比如排序操作; 如果算法具有可逆性质, 可以通过可逆校验的单元测试找出来, 比如加密解密操作。
5、相同类型的参数,传入顺序错误导致
比如,modifyFlow(int rx, int tx), 实际调用为 modifyFlow(tx,rx)
改进措施: 尽可能使类型具体化, 该用浮点数就用浮点数, 该用字符串就用字符串, 该用具体对象类型就用具体对象类型; 相同类型的参数尽可能错开; 如果上述都无法满足, 就必须通过接口测试来验证, 接口参数值务必是不同的。
6、空指针异常
空指针异常通常是对象没有正确初始化, 或者使用对象之前没有对对象是否非空做检测。
改进措施:对于配置对象, 检测其是否成功初始化; 对于普通对象, 获取到实体对象使用之前, 检测是否非空。
7、网络通信错误
网络通信错误通常是因为网络延迟、阻塞或不通导致的错误。网络通信错误通常是小概率事件, 但小概率事件很可能会导致大面积的故障、 难以复现的BUG。
改进措施: 在前一个子系统的结束点和后一个子系统的入口点分别打 INFO 日志。 通过两者的时间差提供一点线索。
8、事务与并发错误
事务与并发结合在一起, 很容易产生非常难以定位的错误。
改进措施: 对于程序中的并发操作, 涉及到共享变量及重要状态修改的, 要加 INFO 日志。更有效的做法???
9、配置错误
改进措施: 在启动应用或启动相应配置时, 检测所有的配置项, 打印相应的INFO日志, 确保所有配置都加载成功。
10、业务不熟悉导致的错误
在中大型系统, 部分业务逻辑和业务交互都比较复杂, 整个的业务逻辑可能存在于多个开发同学的大脑里, 每个人的认识都不是完整的。这很容易导致业务编码错误。
改进措施: 通过多人讨论和沟通, 设计正确的业务用例, 根据业务用例来编写和实现业务逻辑; 最终的业务逻辑和业务用例必须完整存档; 在业务接口中注明该业务的前置条件、处理逻辑、后置校验和注意事项; 当业务变化时, 需要同步更新业务注释; 代码REVIEW。 业务注释是业务接口的重要文档, 对业务理解起着重要的缓存作用。
11、设计问题导致的错误
比如同步串行方式会有性能、响应慢的问题, 而并发异步方式可以解决性能、响应慢的问题, 但会带来安全、正确性的隐患。异步方式会导致编程模型的改变, 新增异步消息推送和接收等新的问题。使用缓存能够提高性能, 但是又会存在缓存更新的问题。
改进措施: 编写和仔细评审设计文档。 设计文档必须阐述背景、需求、所满足的业务目标、要达到的业务性能指标、可能的影响、设计总体思路、详细方案、预见该方案的优缺点及可能的影响; 通过测试和验收, 确保改设计方案确实满足业务目标和业务性能指标。
12、随时间变化而出现的bug
有些解决方案在过去看来是很不错的,但在当前或者未来的情景中可能变得笨拙甚至不中用,也是常见的事情。比如像加密解密算法, 在过去可能认为是完善的, 在破解之后就要慎重使用了。
改进措施: 关注变化以及漏洞修复消息,及时修正过时的代码、库、行为。
13、硬件相关的错误。
比如内存泄露, 存储空间不足, OutOfMemoryError 等。
改进措施: 增加对应用系统的 CPU / 内存 / 网络等重要指标的性能监控。
二、程序错误日志的内容可能存在如下问题
1、错误日志没有指明错误参数和内容
2、错误场景不明确
3、内容不明确, 或不明其义
4、排查问题的引导内容不明确
5、错误内容不够具体细致
6、半英文句式读起来不够清晰明白,需要思考来拼凑起完整的意思
三、如何编写更容易排查问题的错误日志
1、尽可能完整。 每一条错误日志都完整描述了: 什么场景下发生了什么错误, 什么原因(或者哪些可能原因), 如何解决(或解决提示);
2、尽可能具体。 比如 NC 资源不足, 究竟具体指什么资源不足, 是否可以通过程序直接指明; 通用错误,比如 VM NOT EXIST , 要指明在什么场景下发生的,可能便于后续统计的工作。
3、尽可能直接。 最理想的错误日志应该让人在第一直觉下能够知道是什么原因导致,该怎么去解决,而不是还要通过若干步骤去查找真正的原因。
4、将已有经验集成直接到系统中。 所有已经解决过的问题及经验都要尽可能以友好的方式集成到系统中,给新进人员更好的提示,而不是埋藏在其他地方。
5、排版要整洁有序, 格式统一化规范化。 密密麻麻、随笔式的日志看着就揪心, 相当不友好, 也不便于排查问题。
6.、采用多个关键字唯一标识请求,突出显示关键字: 时间、实体标识(比如vmname)、操作名称。
四、编写错误日志总结
错误日志是排查问题的重要手段之一。 当我们编程实现一项功能时, 通常会考虑可能发生的各种错误及相应原因: 要排查出相应的原因, 就需要一些关键描述来定位原因。这就会形成三元组:
错误现象 -> 错误关键描述 -> 最终的错误原因
需要针对每一种错误尽可能提供相应的错误关键描述,从而定位到相应的错误原因。也就是说,编程的时候,要仔细思考, 哪些描述是非常有利于定位错误原因的, 尽可能将这些描述添加到错误日志中。