如何让错误日志更方便排查问题

 2024-01-17 05:01:36  阅读 0

作者|秦水雨

来源|

记录程序中的错误的主要目标是为更好地排除故障和解决问题提供重要的线索和指导。 但在实际应用中,错误日志的内容和格式千差万别,错误信息可能不完整、没有相关背景、意义不明确,使得排查和排除故障成为一项非常不方便或耗时的操作。 事实上,如果你在编程时稍加注意,就会减少很多排查问题时无用的精力。 在解释如何编写有效的错误日志之前,了解错误是如何发生的很重要。

错误是如何犯的

对于当前的系统来说,错误是由三个地方引起的:

1、上层系统引入非法参数。 对于非法参数导致的错误,可以通过参数验证和前置条件验证来拦截错误;

2、与底层系统交互导致的错误。 与下层交互导致的错误有两类:

A。 下层系统处理成功,但出现通信错误,会导致子系统之间数据不一致;

针对这种情况,可以采用超时补偿机制,提前记录任务,后期通过定时任务修正数据。

更好的设计解决方案?

b. 通信成功,但下层处理出现错误。

这种情况下,就需要与下层开发人员进行沟通,协调子系统之间的交互;

需要根据下层返回的错误码和错误描述进行适当的处​​理或给出合理的提示信息。

无论哪种情况,都需要假设底层系统的可靠性是平均的,并对错误进行设计考虑。

3、系统该层处理出现错误。

系统这一层出错的原因:

原因一:疏忽造成。 遗漏意味着程序员本可以避免此类错误,但未能这样做。 例如,&& 输入为 &,== 输入为 =; 边界错误、复合逻辑判断错误等。疏忽要么是由于程序员注意力不集中,如劳累、通宵加班、边开会边写程序; 或者程序员急于实现功能而没有考虑到程序的健壮性等。

改进措施:利用代码静态分析工具和单元测试行覆盖,有效避免此类问题。

原因2:错误和异常处理不足造成。 例如,输入一个问题。 在计算两个数相加时,不仅要考虑计算溢出的问题,还要考虑非法输入的情况。 对于前者,可以通过理解、错误或者经验来避免,而对于后者,必须加以限制,使其在我们智商可以控制的范围内,比如用正则表达式过滤掉非法输入。 必须测试正则表达式。 对于非法输入,尽可能提供详细、易懂、友好的提示信息、原因和建议。

改进措施:尽可能彻底地考虑各种错误情况和异常处理。 实现主流程后,添加一个步骤:仔细考虑各种可能出现的错误和异常,返回合理的错误码和错误描述。 每个接口或模块有效处理自己的错误和异常,可以有效避免复杂场景交互带来的bug。 例如,一个业务用例是通过场景ABC交互完成的。 AB实际执行成功,但C失败。 此时B需要回滚C返回的合理代码和消息,并将合理代码和消息返回给A。A根据B的返回进行回滚,将合理代码和消息返回给客户端。 代码和消息。 这是分段回滚机制,需要每个场景都考虑异常情况下的回滚。

原因三:逻辑耦合过紧造成的。 由于业务逻辑的紧密耦合,随着软件产品一步步发展,各种逻辑关系错综复杂,难以看清全局,导致局部修改的影响波及到全局范围,造成不可预测的问题。

改进措施:写短函数、短方法。 每个函数或方法最好不要超过50行。 编写无状态函数和方法,只读全局状态,相同的前提条件将始终输出相同的结果,并且不会根据外部状态改变其行为; 定义合理的结构、接口和逻辑段,使接口之间的连接交互应尽可能正交和低耦合; 对于服务层,应尽可能提供简单且正交的接口; 应进行持续重构,保持应用程序的模块化和松散耦合,并理清逻辑依赖关系。 对于大量业务接口交互的情况,需要梳理各个业务接口的逻辑流程和相互依赖关系,并进行整体优化; 对于状态数量较多的实体,还需要梳理相关的业务接口,梳理状态之间的转换。 关系。

原因4:算法错误导致。

改进措施:首先将算法与应用分离。 如果算法有多种实现,可以通过交叉检查的单元测试找出来,比如排序操作; 如果算法具有可逆性,可以通过可逆性检查的单元测试来发现,例如加解密操作。

原因5:相同类型的参数传入顺序错误。 例如(int rx, int tx),实际调用的是(tx, rx)

改进措施: 类型尽可能具体。 使用浮点数时使用浮点数,使用字符串时使用字符串,使用特定对象类型时使用特定对象类型; 尽可能错开相同类型的参数; 如果以上都不能满足的话,就必须通过接口测试来验证。 接口参数值必须不同。

原因六:空指针异常。 空指针异常通常发生在对象没有正确初始化,或者在使用对象之前没有检查对象是否为非空时。

改进措施:对于配置对象,检查是否初始化成功; 对于普通对象,在获取使用的实体对象之前,先检查它是否为非空。

原因七:网络通讯错误。 网络通信错误通常是由网络延迟、拥塞或阻塞引起的。 网络通信错误通常是低概率事件,但低概率事件很可能导致大规模故障和难以重现的BUG。

改进措施:在上一个子系统的终点和下一个子系统的入口点分别制作INFO日志。 两者之间的时间差提供了线索。

原因八:事务和并发错误。 事务和并发的结合很容易产生很难定位的错误。

改进措施:对于程序中涉及共享变量和重要状态修改的并发操作,必须添加INFO日志。 更有效的方法? ? ?

原因九:配置错误。

改进措施:启动应用程序或启动相应配置时,检测所有配置项,打印相应的INFO日志,确保所有配置加载成功。

原因十:对业务不熟悉导致的错误。 在中大型系统中,一些业务逻辑和业务交互都比较复杂。 整个业务逻辑可能存在于多个开发同学的大脑中,每个人的理解都是不完整的。 这很容易导致业务编码错误。

改进措施:通过多人讨论和交流,设计正确的业务用例,并根据业务用例编写和实现业务逻辑; 最终的业务逻辑和业务用例必须完整归档; 在业务界面中注明该业务的必备条件、处理逻辑、事后检查及注意事项; 当业务发生变化时,业务注释和代码需要同步更新。 业务注释是业务接口的重要文档,对于业务理解起到重要的缓存作用。

原因11:设计问题导致的错误。 例如,同步串行方式会存在性能和响应慢的问题,而并发异步方式可以解决性能和响应慢的问题,但会给安全性和正确性带来隐患。 异步方式会导致编程模型的改变,增加异步消息推送和接收等新问题。 使用缓存可以提高性能,但是缓存更新会出现问题。

改进措施:撰写并仔细审查设计文件。 设计文件必须描述背景、要求、要达到的业务目标、要达到的业务绩效指标、可能产生的影响、总体设计思路、详细方案,预见方案的优点、缺点和可能产生的影响; 通过测试和验收,确保变更的设计解决方案确实满足业务目标和业务绩效指标。

原因12:未知细节导致的错误。 例如缓冲区溢出和SQL注入攻击。 从功能角度来看,没有问题,但从恶意利用角度来看,却存在漏洞。 再比如,如果你选择一个库来解析JSON字符串,默认情况下,当向对象添加新字段时,会出现解析错误。 必须将 @( = true) 注释添加到对象中才能正确响应更改。 如果选择其他JSON库,可能不会出现这个问题。

改进措施:一方面要积累经验,另一方面要考虑安全问题和异常情况,选择成熟且经过严格测试的库。

原因 13:随着时间的推移会出现错误。 过去看起来很棒的解决方案在当前或未来的场景中变得笨拙甚至无用,这种情况并不罕见。 例如,加密和解密算法在过去可能被认为是完美的,但在被破解后就应该谨慎使用。

改进措施:关注变更和漏洞修复消息,及时纠正过时的代码、库和行为。

原因十四:硬件相关错误。 比如内存泄漏、存储空间不足等。

改进措施:增加应用系统CPU/内存/网络等重要指标的性能监控。

系统常见错误:

1、实体记录在数据库中不存在,必须表明它是哪个实体或实体标识符;

2.实体配置不正确。 您必须指定哪些配置有问题以及正确的配置应该是什么;

3、如果实体资源不满足条件,必须明确当前资源是什么,资源需求是什么;

4、不具备实体经营的前提条件。 您必须指定需要满足哪些先决条件以及当前状态是什么;

5、实体经营事后检查不合格。 您必须指定需要满足哪些事后检查以及当前状态是什么;

6.性能问题导致超时。 您必须指定导致性能问题的原因以及将来如何优化它们;

7、多个子系统之间交互通讯错误导致状态或数据不一致?

一般很难定位的错误会出现在比较低级别的地方。 由于底层无法预测具体的业务场景,所以给出的错误信息比较笼统。

这就需要业务上层提供尽可能多的线索。 该错误必定是由于多个系统或层交互过程中堆栈的某一层不满足前提条件而导致的。 编程时,尽量保证栈的每一层都满足所有必要的前置条件,尽可能避免向底层传递错误的参数,并尽可能在业务层拦截错误。

大多数错误都是由多种原因综合造成的。 但每一个错误都必须有其原因。 解决错误后,深入分析错误是如何发生的以及如何避免错误再次发生。 努力才会成功,反思才会进步!

如何编写错误日志以便更轻松地进行故障排除

记录错误的基本原则:

1. 尽可能完整。 每个错误日志都完整描述:在什么场景下发生了什么错误,原因是什么(或可能的原因),以及如何解决(或解决提示);

2. 尽可能具体。 比如NC资源不足,具体指什么,能否直接通过程序指定; 对于一般错误,如VM NOT EXIST,需要明确其发生的场景,以便于后续的统计工作。

3. 尽可能直接。 理想的错误日志应该让人第一时间直觉就知道原因以及如何解决,而不是必须经过几个步骤才能找到真正的原因。

4、将现有经验直接融入到系统中。 所有已经解决的问题和经验都应该以尽可能友好的方式融入到系统中,给新员工更好的提示,而不是被埋在别处。

5、版式应整齐有序,格式统一规范。 密密麻麻的、像论文一样的日志看着让人心痛。 他们对于解决问题非常不友好且不方便。

6. 使用多个关键字唯一标识请求并突出显示关键字:时间、实体标识符(例如)、操作名称。

故障排除的基本步骤:

登录应用服务器->打开日志文件->定位错误日志位置->排查问题,根据错误日志线索的指导确认并解决问题。

在:

1、从登录到打开日志文件:由于应用服务器有多台,不方便一一登录查看。 需要写一个工具放在AG上,直接在AG上查看所有服务器日志,甚至直接过滤掉需要的错误日志。

2. 找到错误日志位置。 目前日志布局密集,错误日志定位困难。 一般可以先用“time”定位到错误日志靠近前面的位置,然后用实体关键字/操作名组合来锁定错误日志的位置。 虽然定位错误日志比较传统,但必须先找到,而且不具备描述性。 最好直接根据时间/内容关键字来定位错误日志位置。

3. 分析错误日志。 错误日志的内容应该更加直接、清晰,明确表明与当前要排查问题的特征相符,并给出重要线索。

通常,程序错误日志的问题是只能根据当前代码上下文来理解日志内容。 看起来简洁,但总是以半英文格式写得不完整; 一旦离开代码上下文,就很难知道在说什么。 让人思考或者看代码就能明白日志的含义。 这不是自找苦受吗?

例如:

if ((storageType == StorageType.dfs1 || storageType == StorageType.dfs2)
                && (zone.hasStorageType(StorageType.io3) || zone.hasStorageType(StorageType.io4))) {
// 进入dfs1 和dfs2 在io3 io4 存储。
else {
      log.info("zone storage type not support, zone: " + zone.getZoneId() + ", storageType: "
+ storageType.name());
      throw new BizException(DeviceErrorCode.ZONE_STORAGE_TYPE_NOT_SUPPORT);
}

区域支持什么类型是正确的? 别让我思考!

即使您离开代码上下文,错误日志也应该能够清楚地描述发生的情况。

另外,如果能直接在错误日志中明确说明原因,在制作检查日志时可以省一些力气。

从某种意义上说,错误日志也可以是一个非常有用的文档,记录各种非法运行用例。

当前程序错误日志内容可能存在以下问题:

1、错误日志未注明错误参数及内容:

catch(Exception ex){
      log.error("control ip insert failed", ex);
      return new ResultSet(
ControlIpErrorCode.ERROR_CONTROL_IP_INSERT_FAILURE);
}

插入失败的ip未指定。 如果加上ip关键字,就更容易搜索并锁定错误。

类似的还有:

log.error("获取一些时间及其IP。添加或IP。", e);

它没有指定它属于哪个IP。 值得注意的是,指定这些需要一些额外的工作,这可能会稍微影响性能。 这是需要权衡性能和可调试性的地方。

解决方案:使用.("Some msg to : %s", )方法来指示错误参数和内容。

这通常需要在 DO 对象上编写可读的方法。

2、错误场景不清楚:

log.error("nc已存在,nc ip" + .getIp());

检测到NC已经存在,报错。 但日志中并没有具体说明错误场景,让人猜测为什么会报告 NC 现有错误。

可以改为

log.error("想要 nc 时 nc 已存在,请检查 nc 。给定 nc ip: " + .getIp());

log.error("[ nc] nc 已存在,检查 nc 。给定 nc ip: " + .getIp());

类似的还有:

log.error("并非所有 vm , nc id " + .());

更改为 log.error("[ nc] nc 中的某些虚拟机 [%s] 不是。 nc id: %s", , .());

解决方案:在错误信息中添加“when”一词,或者在错误信息前添加“接口名称”以指示错误场景。 直接从错误日志中就可以了解。

一般来说,如果你知道的话,可以添加[接口名称]和“何时”一词。

3、内容不明确或者意思不明确:

if(aliMonitorReporter == null) {
        log.error("aliMonitorReporter is null!");
else {
       aliMonitorReporter.attach(new ThreadPoolMonitor(namePrefix, asynTaskThreadPool.getThreadPoolExecutor()));
}

改为:log.error("为null,不是,签入文件xxx。");

类似的还有:

if (diskWbps == null && diskRbps == null && diskWiops == null    && diskRiops == null) {
      log.error("none of attribute is specified for modifying");
      throw new BizException(DeviceErrorCode.NO_ATTRIBUTE_FOR_MODIFY);
}

更改为 log.error("[ disk ] [,,,] 均不适用于磁盘 id:" + );

解决方案:更清楚、更恰当地描述错误。

4、故障排除指导内容不清楚:

log.error("获取gw组ip.:" + .(.()));

? 如何解决这个问题? 我应该去找谁? 到哪里寻找更具体的线索?

解决方案:添加相应的背景知识和引导的故障排除措施。

5、错误内容不够具体:

if (!ncResourceService.isNcResourceEnough(ncResourceDO,    vmResourceCondition)) {
      log.error("disk space is not enough at vm's nc, nc id:" + vmDO.getNcId());
      throw new BizException(ResourceErrorCode.ERROR_RESOURCE_NOT_ENOUGH);
}

到底什么是资源匮乏? 现在还剩多少? 现在需要多少? 值得注意的是,指定这些需要一些额外的工作,并且可能会稍微影响性能。 这是需要权衡性能和可调试性的地方。

解决方案:通过改进程序或程序技术,尽可能揭示具体差异,减少人工比较操作。

6、半英文句子结构阅读起来不够清晰,需要思考才能拼凑出完整的意思:

log.warn("缓存, id "+.getId()+" db "+.() +", nc "+ );

改成:

log.warn(.("[查询缓存]缓存和 nc,'%s' 中的为 %s ,但 nc 中为 %s。", .getId(), .(), ));

解决方案:改为自然易读的英语句子结构。

总而言之,错误日志格式可以是:

log.error("[接口名称或操作名称][某些错误消息].[][].[需要执行的操作].");

log.error(.("[接口名称或操作名称][某些错误消息].[%s].[].[需要执行的操作].", ));

或者

log.error("[Some Error Msg]在[某些].[].[需要做的事情]时出现错误参数或内容。");

log.error(.("[在某些情况下].[].[需要做]时将[某些错误消息]发送给%s。", ));

[ ]。 [ 需要做]。 某些情况下可以省略; 最好在一些重要的接口和场景中进行解释。

每个错误日志都是独立的。 应尽可能完整、具体、直接地说明在何种场景下发生了什么错误、导致错误的原因以及应采取哪些措施或步骤。

问题:

1.. 性能会影响日志记录吗? 一般来说,错误日志应该比较少,使用频率也高。 不会太高,不会影响应用程序和日志。

2.当开发时间很紧的时候,你有时间考虑的话吗? 建立标准化的内容格式并将内容融入该格式可以节省思考文字的时间。

3.什么时候使用info、warn、error?

info用于打印程序正常应该出现的状态信息,方便跟踪定位;

warn表示系统稍有不合理,但不影响运行和使用;

error表示系统发生错误或异常,导致目标操作无法正常完成。

错误日志是排查问题的重要手段之一。 当我们编程实现一个功能时,通常会考虑可能出现的各种错误以及相应的原因:

为了找出相应的原因,需要一些关键的描述来定位原因。 这将形成一个三元组:

错误现象->错误关键描述->最终错误原因。

需要对每个错误尽可能提供相应的错误关键描述,以定位相应的错误原因。

也就是说,在编程时,一定要仔细思考哪些描述对于定位错误原因非常有帮助,并尽可能将这些描述添加到错误日志中。

如果还有文章中未指出的问题或困难,请提出您的建议。

如果您需要前几期的资源,请自行获取。

喜欢就“看”吧^_^

如本站内容信息有侵犯到您的权益请联系我们删除,谢谢!!


Copyright © 2020 All Rights Reserved 京ICP5741267-1号 统计代码