前言
排查程序问题时,错误日志就是最靠谱的"地图":写得好的日志,能直接带你找到"问题地点";写得差的日志,却像一张模糊的涂鸦,让你在代码里绕来绕去还找不到北。今天就从"错误怎么来的"讲到"日志怎么写",来讲讲错误日志的妙用。
一、错误不是"凭空冒出来"的,这3类场景最容易出问题
程序出错从来不是"突然袭击",而是有明确来源的。就像生活里的麻烦事,要么是别人给的(上层传错参数),要么是和别人打交道出的(下层交互故障),要么是自己没做好(本层处理疏漏),具体可以分为3类:
1. 上层系统"递错了料":非法参数引入错误
比如你写了个"计算两数相加"的功能,上层系统却传了"abc"这种非数字参数。这类错误能通过"参数校验"提前拦截,比如用正则表达式过滤非法输入,给用户提示"请输入数字"。
2. 下层系统"没配合好":交互过程出故障
和下层系统(比如数据库、其他服务)打交道时,容易出两种问题:
- 通信断了,数据没同步:下层其实处理完了,但信号没传回来(比如网络断了),导致两边数据对不上;
- 通信通了,处理错了:信号传过去了,但下层没处理好(比如数据库返回"表不存在"),这时候要和下层开发沟通,按返回的错误码做处理,比如提示"请检查数据库表配置"。
不管哪种情况,都要默认"下层可能不靠谱",提前做好预案。
3. 本层系统"自己出了岔子":处理逻辑有疏漏
这是最常见的错误来源:
- 手滑疏忽 :把
&&
写成&
、==
写成=
,或者边界判断错(比如"大于等于"写成"大于")------就像打字时漏了个键,解决办法是用代码静态分析工具(比如SonarQube)、写单元测试覆盖逻辑; - 异常没考虑全:计算相加时没考虑"溢出",输入时没过滤"非法字符"------比如算10亿+10亿,结果超出整数范围,解决办法是写功能后多问自己:"如果传错值、算错数,该怎么提示?";
- 逻辑像"一团乱麻":函数写了200行,各种逻辑缠在一起,改一个地方牵一发全身------解决办法是拆成短函数(最好不超过50行),像"拆快递"一样把复杂逻辑分开,每个函数只干一件事;
- 空指针"找上门" :对象没初始化就用(比如
user.getName()
里user
是null
)------解决办法是用之前先检查:"这个对象是不是空的?",配置对象还要确认"有没有加载成功"; - 配置"装睡":启动时配置没加载(比如数据库地址写错),却没提示------解决办法是启动时打印INFO日志,确认"所有配置都读对了",比如"数据库地址:jdbc:mysql://xxx"。
二、核心技巧:错误日志这么写,排查问题快10倍
很多人写日志只写"xxx失败",比如log.error("插入IP失败")
------这种日志就像"地图上只标了'有宝藏',没标具体在哪"。真正有用的日志,要满足6个原则,每个原则都配"反面例子+正面例子":
原则1:尽可能完整------把"时间、场景、原因、办法"说全
反面:
bash
log.error("control ip insert failed", ex);
(没说哪个IP失败,不知道为啥失败)
正面:
bash
log.error("[插入控制IP] 插入失败,失败IP:{},可能原因:IP格式错误/数据库连接超时,建议:检查IP是否为xxx格式/查看数据库状态", ip, ex);
一句话讲清"在做什么时失败、哪个参数错了、可能为啥、该咋办"。
原则2:尽可能具体------别用"通用词",要"精准到细节"
反面:
bash
log.error("zone storage type not support, zone: " + zone.getZoneId() + ", storageType: " + storageType.name());
(没说支持啥类型)
正面:
bash
og.error("[检查zone存储] zone不支持该存储类型,zoneID:{},传入类型:{},支持类型:dfs1/dfs2(需搭配io3/io4),建议:修改zone存储配置", zone.getZoneId(), storageType.name());
连"正确的配置是什么"都说明白,不用再查代码。
原则3:尽可能直接------让人"一眼懂",不用"猜半天"
反面:
bash
`log.error("aliMonitorReporter is null!");`
(为啥为null?咋解决?)
正面:
bash
`log.error("[初始化监控] aliMonitorReporter为null,可能是配置文件xxx没加载,建议:检查xxx.conf里的monitor配置是否正确");`
原则4:集成经验------把"踩过的坑"写进日志
比如"Jackson解析JSON新增字段报错",解决后在日志里加提示:
bash
log.error("[JSON解析] 新增字段导致解析失败,建议:在实体类加@JsonIgnoreProperties(ignoreUnknown = true)注解,参考之前解决的#123问题");
让后来人不用再踩同样的坑。
原则5:格式统一------别像"随笔",要像"表格"
乱糟糟的日志看着头疼,建议套用固定格式:
bash
log.error("[接口名/操作名] [错误现象],[相关参数],[可能原因],[解决建议]");
比如:
bash
log.error("[删除NC] 无法删除NC,NCID:{},未销毁VM:{},可能原因:VM还在运行,建议:先销毁VM再删除NC", ncId, vmNames);
原则6:突出关键字------时间、ID、操作名要显眼
日志里必须包含"时间(精确到秒)、实体ID(比如NCID、VM名)、操作名",比如:
bash
2024-10-01 14:30:00 [删除NC] 无法删除NC,NCID:nc001,未销毁VM:vm001、vm002...
定位时用"时间+NCID"搜,比用requestId快多了。
三、QA
1. 用String.format写日志会影响性能吗?
不会!错误日志本来就少(正常情况下不会频繁报错),String.format的调用频率低,对程序没影响,放心用。
2. 开发忙的时候,没时间写详细日志咋办?
套固定格式!把[接口名] [错误] [参数] [原因] [建议]
存在记事本里,写日志时填内容就行,比如:
[新增VM] 新增失败,VM名:vm001,可能原因:CPU资源不足,建议:检查NC资源
比瞎写快,还规范。
3. info、warn、error该怎么分?
- info:正常状态(比如"初始化成功,配置:xxx"),用来"追踪流程";
- warn:小问题不影响运行(比如"缓存过期,已自动刷新");
- error:大问题没法完成操作(比如"删除NC失败"),必须处理。
四、错误日志是"反思的镜子",更是"传承的文档"
解决完错误后,要回头看日志:"当时漏了什么信息?下次怎么写更清楚?"。好的错误日志不仅能帮你快速排查问题,还能成为团队的"知识库",记录下所有"不合法的运行用例",让新人少走弯路。