作者:琴水玉 \
起源:https://cnblogs.com/lovesqcc/…
在程序中打谬误日志的次要指标是为更好地排查问题和解决问题提供重要线索和领导。然而在理论中打的谬误日志内容和格局变动多样,谬误提醒上可能残缺不全、没有相干背景、不明其义,使得排查解决问题成为十分不不便或者耗时的操作。而实际上,如果编程的时候稍加用心,就会缩小排查问题的很多无用功。
在论述如何编写无效的谬误日志之前,理解谬误是怎么产生的,十分重要。
谬误是如何炼成的
对于以后零碎来说,谬误的产生由三个中央引入:
1. 下层零碎引入的非法参数。对于非法参数引入的谬误,能够通过参数校验和前置条件校验来截获谬误;
2. 与上层零碎交互产生的谬误。与上层交互产生的谬误,有两种:
a. 上层零碎解决胜利了,然而通信出错了,这样会导致子系统之间的数据不统一;
对于这种状况,能够采纳超时弥补机制,事后将工作记录下来,通过定时工作在后续将数据勘误过去。
更好的设计方案?
b. 通信胜利了,然而上层解决出错了。
对于这种状况,须要与上层开发人员沟通,协调子系统之间的交互;
须要依据上层返回的错误码和谬误形容做适当的解决或给予正当的提示信息。
无论哪一种状况,都要假如上层系统可靠性个别,做好出错的设计思考。
3. 本层零碎解决出错。
本层零碎产生谬误的起因:
起因一:忽略导致。
忽略是指程序员能力齐全可防止此类谬误但实际上没做到。比方将 && 敲成了 &,== 敲成了 =;边界谬误,复合逻辑判断谬误等。忽略要么是程序员注意力不够集中,比方处于困倦状态、加班通宵、边散会边写程序;要么是急着实现性能,没有顾及程序的健壮性等。
改良措施:应用代码动态剖析工具,通过单元测试行笼罩可无效防止此类问题。
起因二:谬误与异样解决不够周全导致的。
比方输出问题。计算两个数相加,不仅要思考计算溢出问题,还要思考输出非法的情景。对于前者,可能通过理解、犯错或教训就能够防止,而对于后者,则必须加以限定,以使之处于咱们的智商可能管制的范畴内,比方应用正则表达式过滤掉不非法的输出。对于正则表达式必须进行测试。对于不非法输出,要给出尽可能具体、易懂、敌对的提示信息、起因及倡议计划。
改良措施:尽可能周全地思考各种谬误情景和异样解决。在实现主流程之后,减少一个步骤:认真斟酌可能的各种谬误和异样,返回正当错误码和谬误形容。每个接口或模块都无效解决好本人的谬误和异样,可无效防止因场景交互简单导致的 bug。
譬如,一个业务用例由场景 A.B.C 交互实现。理论执行 A.B 胜利了,C 失败了,这时 B 须要依据 C 返回正当的代码和音讯进行回滚并返回给 A 正当的代码和音讯,A 依据 B 的返回进行回滚,并返回给客户端正当的代码和音讯。这是一种分段回滚的机制,要求每个场景都必须思考异常情况下的回滚。
起因三:逻辑耦合严密导致。
因为业务逻辑耦合严密,随着软件产品一步步倒退,各种逻辑关系盘根错节,难以看到全局情况,导致部分批改影响波及到全局范畴,造成不可预知的问题。
改良措施:编写短函数和短办法,每个函数或办法最好不超过 50 行。编写无状态函数和办法,只读全局状态,雷同的前提条件总是会输入雷同的后果,不会依赖内部状态而变更本人的行为;定义正当的构造、接口和逻辑段,使接口之间的交互尽可能正交、低耦合;对于服务层,尽可能提供简略、正交的接口;继续重构,放弃利用模块化和松耦合,理清逻辑依赖关系。对于有大量业务接口相互影响的状况,必须整顿各个业务接口的逻辑流程及相互依赖关系,从整体上进行优化;对于有大量状态的实体,也须要梳理相干的业务接口,整顿状态之间的转换关系。
起因四:算法不正确导致。
改良措施:首先将算法从利用中分离出来。若算法有多种实现,能够通过穿插校验的单元测试找进去,比方排序操作;如果算法具备可逆性质,能够通过可逆校验的单元测试找进去,比方加密解密操作。
起因五:雷同类型的参数,传入程序谬误导致。
比方,modifyFlow(int rx, int tx), 理论调用为 modifyFlow(tx,rx)
改良措施:尽可能使类型具体化,该用浮点数就用浮点数,该用字符串就用字符串,该用具体对象类型就用具体对象类型;雷同类型的参数尽可能错开;如果上述都无奈满足,就必须通过接口测试来验证,接口参数值务必是不同的。
起因六:空指针异样。
空指针异样通常是对象没有正确初始化,或者应用对象之前没有对对象是否非空做检测。
改良措施:对于配置对象,检测其是否胜利初始化;对于一般对象,获取到实体对象应用之前,检测是否非空。
起因七:网络通信谬误。
网络通信谬误通常是因为网络提早、阻塞或不通导致的谬误。网络通信谬误通常是小概率事件,但小概率事件很可能会导致大面积的故障、难以复现的 BUG。
改良措施:在前一个子系统的完结点和后一个子系统的入口点别离打 INFO 日志。通过两者的时间差提供一点线索。
起因八:事务与并发谬误。
事务与并发联合在一起,很容易产生十分难以定位的谬误。
改良措施:对于程序中的并发操作,波及到共享变量及重要状态批改的,要加 INFO 日志。更无效的做法???
起因九:配置谬误。
改良措施:在启动利用或启动相应配置时,检测所有的配置项,打印相应的 INFO 日志,确保所有配置都加载胜利。
起因十:业务不相熟导致的谬误。
在中大型零碎,局部业务逻辑和业务交互都比较复杂,整个的业务逻辑可能存在于多个开发同学的大脑里,每个人的意识都不是残缺的。这很容易导致业务编码谬误。
改良措施:通过多人探讨和沟通,设计正确的业务用例,依据业务用例来编写和实现业务逻辑;最终的业务逻辑和业务用例必须残缺存档;在业务接口中注明该业务的前置条件、解决逻辑、后置校验和注意事项;当业务变动时,须要同步更新业务正文;代码 REVIEW。业务正文是业务接口的重要文档,对业务了解起着重要的缓存作用。
起因十一:设计问题导致的谬误。
比方同步串行形式会有性能、响应慢的问题,而并发异步形式能够解决性能、响应慢的问题,但会带来平安、正确性的隐患。异步形式会导致编程模型的扭转,新增异步音讯推送和接管等新的问题。应用缓存可能进步性能,然而又会存在缓存更新的问题。
改良措施:编写和认真评审设计文档。设计文档必须论述背景、需要、所满足的业务指标、要达到的业务性能指标、可能的影响、设计总体思路、具体计划、预感该计划的优缺点及可能的影响;通过测试和验收,确保改设计方案的确满足业务指标和业务性能指标。
起因十二:未知细节问题导致的谬误。
比方缓冲区溢出、SQL 注入攻打。从性能上看是没有问题的,然而从歹意应用上看,是存在破绽的。再比方,抉择 jackson 库做 JSON 字符串解析,默认状况下,当对象新增字段时会导致解析出错。必须在对象上加 @JsonIgnoreProperties(ignoreUnknown = true) 注解能力正确应答变动。如果选用其余 JSON 库就不肯定有这个问题。
改良措施:一方面要通过教训积攒,另一方面,思考平安问题和例外情况,抉择成熟的通过严格测试的库。
起因十三:随工夫变动而呈现的 bug。
有些解决方案在过来看来是很不错的,但在以后或者将来的情景中可能变得蠢笨甚至不中用,也是常见的事件。比方像加密解密算法,在过来可能认为是欠缺的,在破解之后就要谨慎应用了。
改良措施:关注变动以及破绽修复音讯,及时修改过期的代码、库、行为。
起因十四:硬件相干的谬误。
比方内存泄露,存储空间有余,OutOfMemoryError 等。另外,关注公众号 Java 技术栈,在后盾回复:JVM46,能够获取一份 46 页的 JVM 教程,十分齐全。
改良措施:减少对利用零碎的 CPU / 内存 / 网络等重要指标的性能监控。Java 系列最新教程:https://github.com/javastacks…
零碎呈现的常见谬误:
1. 实体在数据库中的记录不存在,必须指明是哪个实体或实体标识;
2. 实体配置不正确,必须指明是哪个配置有问题,正确的配置应该是什么;
3. 实体资源不满足条件,必须指明以后资源是什么,资源要求是什么;
4. 实体操作前置条件不满足,必须指明须要满足什么前置条件,以后的状态是什么;
5. 实体操作后置校验不满足,必须指明须要满足什么后置校验,以后的状态是什么;
6. 性能问题导致超时,必须指明是什么导致的性能问题,后续如何优化;
7. 多个子系统交互通信出错导致之间的状态或数据不统一?
个别难以定位的谬误会呈现在比拟底层的中央。因为底层无奈预知具体的业务场景,给出的谬误音讯都是比拟通用的。
这就要求在业务下层提供尽可能丰盛的线索。谬误的产生肯定是多个零碎或档次交互的过程中在某一层栈上不满足前置条件导致。在编程时,在每一层栈中尽可能确保所有必须的前置条件满足,尽可能防止谬误的参数传递到底层,尽可能地将谬误截获在业务层。
大多数谬误都是由多种起因组合产生。但每一种谬误必然有其起因。在解决谬误之后,要深入分析谬误是如何产生的,如何防止这些谬误再次发生。致力就能胜利,然而: 反思能力提高!
如何编写更容易排查问题的谬误日志
打谬误日志的根本准则:
1. 尽可能残缺。每一条谬误日志都残缺形容了:什么场景下产生了什么谬误,什么起因(或者哪些可能起因),如何解决(或解决提醒);
2. 尽可能具体。比方 NC 资源有余,到底具体指什么资源有余,是否能够通过程序间接指明;通用谬误,比方 VM NOT EXIST,要指明在什么场景下产生的,可能便于后续统计的工作。
3. 尽可能间接。最现实的谬误日志应该让人在第一直觉下可能晓得是什么起因导致,该怎么去解决,而不是还要通过若干步骤去查找真正的起因。
4. 将已有教训集成间接到零碎中。所有曾经解决过的问题及教训都要尽可能以敌对的形式集成到零碎中,给新进人员更好的提醒,而不是埋藏在其余中央。
5. 排版要整洁有序,格局统一化规范化。稀稀拉拉、随笔式的日志看着就揪心,相当不敌对,也不便于排查问题。
6. 采纳多个关键字惟一标识申请,突出显示关键字:工夫、实体标识(比方 vmname)、操作名称。
排查问题的根本步骤:
登录到应用服务器 -> 关上日志文件 -> 定位到谬误日志地位 -> 依据谬误日志的线索的领导去排查、确认问题和解决问题。
其中:
1. 从登陆到关上日志文件:因为应用服务器有多台,要逐个登录下来查看切实不不便。须要编写一个工具放在 AG 上间接在 AG 上查看所有服务器日志,甚至间接筛选出所须要的谬误日志。
2. 定位谬误日志地位。目前日志的排版稀稀拉拉,不易定位到谬误日志。个别能够先采纳 ” 工夫 ” 来定位到谬误日志的左近后面的中央,而后应用 实体关键字 / 操作名称 组合来锁定谬误日志中央。依据 requestId 定位谬误日志尽管比拟合乎传统,然而要先找到 requestId , 并且不具备描述性。最好能间接依据工夫 / 内容关键字来定位谬误日志地位。
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);
}
zone 要反对什么 storage type 才是正确的? Do Not Let Me Think !
谬误日志应该做到:即便来到代码情境,也能清晰地形容产生了什么。
此外,如果可能间接在谬误日志中阐明分明起因,在做巡检日志的时候也能够省些力量。
从某种意义上来说,谬误日志也能够是一种十分无益的文档,记录着各种不非法的运行用例。
目前程序谬误日志的内容可能存在如下问题:
1. 谬误日志没有指明谬误参数和内容:
catch(Exception ex){log.error("control ip insert failed", ex);
return new ResultSet<AddControlIpResponse>(ControlIpErrorCode.ERROR_CONTROL_IP_INSERT_FAILURE);
}
没有指明插入失败的 control ip. 如果加上 control ip 关键字,更容易搜寻和锁定谬误。
相似的还有:
log.error("Get some errors when insert subnet and its IPs into database. Add subnet or IP failure.", e);
没有指明是哪个 subnet 的它上司的哪些 IP. 值得注意的是,要指明这些要额定做一些事件,可能会略微影响性能。这时候须要衡量性能和可调试性。
解决方案:应用 String.format(“Some msg to ErrorObj: %s”, errobj) 办法指明谬误参数及内容。
这通常要求对 DO 对象编写可读的 toString 办法。
2. 谬误场景不明确:
log.error(“nc has exist, nc ip” + request.getIp());
在 createNc 中检测到 NC 曾经存在报错。然而日志上没有指明谬误场景,让人猜想,为什么会报 NC 已存在谬误。
能够改为
log.error("nc has exist when want to create nc, please check nc parameters. Given nc ip:" + request.getIp());
log.error("[create nc] nc has exist, please check nc parameters. Given nc ip:" + request.getIp());
相似的还有:
log.error("not all vm destroyed, nc id" + request.getNcId());
改成
log.error("[delete nc] some vms [%s] in the nc are not destroyed. nc id: %s", vmNames, request.getNcId());
解决方案:谬误音讯加上 when 字句,或者谬误音讯前加上【接口名】, 指明谬误场景,间接从谬误日志就晓得明确了。
个别可能晓得 executor 的能够加上【接口名】,service 加上 when 字句。
3. 内容不明确, 或不明其义:
if(aliMonitorReporter == null) {log.error("aliMonitorReporter is null!");
} else {aliMonitorReporter.attach(new ThreadPoolMonitor(namePrefix, asynTaskThreadPool.getThreadPoolExecutor()));
}
改为:
log.error("aliMonitorReporter is null, probably not initialized properly, please check configuration in file 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("[modify disk attribute] None of [diskWbps,diskRbps,diskWiops,diskRiops] is specified for disk id:" + diskId);
解决方案:更清晰贴切地形容谬误内容。
4. 排查问题的疏导内容不明确:
log.error("get gw group ip segment failed. zkPath:" + LockResource.getGwGroupIpSegmnetLockPath(request.getGwGroupId()));
zkPath ? 如何去排查这个问题?我该去找谁?到哪里去查找更具体的线索?
解决方案:加上相应的背景常识和疏导排查措施。
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("cache status conflict, device id"+deviceDO.getId()+"db status"+deviceDO.getStatus() +", nc status"+ status);
改为:
log.warn(String.format("[query cache status] device cache status conflicts between regiondb and nc, status of device'%s'in regiondb is %s , but is %s in nc.", deviceDO.getId(), deviceDO.getStatus(), status));
解决方案:改为天然可读的英文句式。
总结起来,谬误日志格局能够为:
log.error("[ 接口名或操作名] [Some Error Msg] happens. [params] [Probably Because]. [Probably need to do].");
log.error(String.format("[ 接口名或操作名] [Some Error Msg] happens. [%s]. [Probably Because]. [Probably need to do].", params));
或
log.error("[Some Error Msg] happens to 谬误参数或内容 when [in some condition]. [Probably Because]. [Probably need to do].");
log.error(String.format("[Some Error Msg] happens to %s when [in some condition]. [Probably Because]. [Probably need to do].", parameters));
[Probably Reason]. [Probably need to do]. 在某些状况下能够省略;在一些重要接口和场景下最好能阐明一下。
每一条谬误日志都是独立的,尽可能残缺、具体、间接阐明何种场景下产生了什么谬误,由什么起因导致,要采纳什么措施或步骤。
问题:
1.String.format 的性能会影响打日志吗?一般来说,谬误日志应该是比拟少的,应用 String.format 的频度并不会太高,不会对利用和日志造成影响。
2. 开发工夫十分缓和时,有工夫去斟酌字句吗?建设一个标准化的内容格局,将内容往格局套,能够节俭斟酌字句的工夫。
3. 什么时候应用 info, warn , error ?
info 用于打印程序应该呈现的失常状态信息,便于追踪定位;
warn 表明零碎呈现轻微的不合理但不影响运行和应用;
error 表明呈现了零碎谬误和异样,无奈失常实现指标操作。
http://stackoverflow.com/ques…
谬误日志是排查问题的重要伎俩之一。当咱们编程实现一项性能时,通常会思考可能产生的各种谬误及相应起因:
要排查出相应的起因,就须要一些要害形容来定位起因。这就会造成三元组:
谬误景象 -> 谬误要害形容 -> 最终的谬误起因。
须要针对每一种谬误尽可能提供相应的谬误要害形容,从而定位到相应的谬误起因。
也就是说,编程的时候,要认真思考,哪些形容是十分有利于定位谬误起因的,尽可能将这些形容增加到谬误日志中。
文中没有指出的问题或艰难,请提出你的倡议。
近期热文举荐:
1.600+ 道 Java 面试题及答案整顿 (2021 最新版)
2. 终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!
3. 阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!
4.Spring Cloud 2020.0.0 正式公布,全新颠覆性版本!
5.《Java 开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞 + 转发哦!