程序出错瞎找?教你写“会说话”的错误日志,秒定位原因

前言

排查程序问题时,错误日志就是最靠谱的"地图":写得好的日志,能直接带你找到"问题地点";写得差的日志,却像一张模糊的涂鸦,让你在代码里绕来绕去还找不到北。今天就从"错误怎么来的"讲到"日志怎么写",来讲讲错误日志的妙用。

一、错误不是"凭空冒出来"的,这3类场景最容易出问题

程序出错从来不是"突然袭击",而是有明确来源的。就像生活里的麻烦事,要么是别人给的(上层传错参数),要么是和别人打交道出的(下层交互故障),要么是自己没做好(本层处理疏漏),具体可以分为3类:

1. 上层系统"递错了料":非法参数引入错误

比如你写了个"计算两数相加"的功能,上层系统却传了"abc"这种非数字参数。这类错误能通过"参数校验"提前拦截,比如用正则表达式过滤非法输入,给用户提示"请输入数字"。

2. 下层系统"没配合好":交互过程出故障

和下层系统(比如数据库、其他服务)打交道时,容易出两种问题:

  • 通信断了,数据没同步:下层其实处理完了,但信号没传回来(比如网络断了),导致两边数据对不上;
  • 通信通了,处理错了:信号传过去了,但下层没处理好(比如数据库返回"表不存在"),这时候要和下层开发沟通,按返回的错误码做处理,比如提示"请检查数据库表配置"。

不管哪种情况,都要默认"下层可能不靠谱",提前做好预案。

3. 本层系统"自己出了岔子":处理逻辑有疏漏

这是最常见的错误来源:

  • 手滑疏忽 :把&&写成&==写成=,或者边界判断错(比如"大于等于"写成"大于")------就像打字时漏了个键,解决办法是用代码静态分析工具(比如SonarQube)、写单元测试覆盖逻辑;
  • 异常没考虑全:计算相加时没考虑"溢出",输入时没过滤"非法字符"------比如算10亿+10亿,结果超出整数范围,解决办法是写功能后多问自己:"如果传错值、算错数,该怎么提示?";
  • 逻辑像"一团乱麻":函数写了200行,各种逻辑缠在一起,改一个地方牵一发全身------解决办法是拆成短函数(最好不超过50行),像"拆快递"一样把复杂逻辑分开,每个函数只干一件事;
  • 空指针"找上门" :对象没初始化就用(比如user.getName()usernull)------解决办法是用之前先检查:"这个对象是不是空的?",配置对象还要确认"有没有加载成功";
  • 配置"装睡":启动时配置没加载(比如数据库地址写错),却没提示------解决办法是启动时打印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失败"),必须处理。

四、错误日志是"反思的镜子",更是"传承的文档"

解决完错误后,要回头看日志:"当时漏了什么信息?下次怎么写更清楚?"。好的错误日志不仅能帮你快速排查问题,还能成为团队的"知识库",记录下所有"不合法的运行用例",让新人少走弯路。

相关推荐
重生之我要当java大帝2 小时前
java微服务-尚医通-管理平台前端搭建-医院设置管理-4
java·开发语言·前端
以己之2 小时前
详解TCP(详细版)
java·网络·tcp/ip
LiuYaoheng2 小时前
【Android】布局优化:include、merge、ViewStub的使用及注意事项
android·java
RealmElysia2 小时前
CoAlbum 引入ES
java·elasticsearch
养海绵宝宝的小蜗2 小时前
Linux 例行性工作任务(定时任务)知识点总结
linux·运维·服务器
せいしゅん青春之我3 小时前
[JavaEE初阶]网络协议-状态码
java·网络协议·http
shepherd1113 小时前
JDK源码深潜(一):从源码看透DelayQueue实现
java·后端·代码规范
乌萨奇也要立志学C++3 小时前
【Linux】基础IO(二)深入理解“一切皆文件” 与缓冲区机制:从原理到简易 libc 实现
linux·运维·服务器
这周也會开心3 小时前
通过ssh连接GitHub远程仓库
运维·ssh·github
天天摸鱼的java工程师3 小时前
SpringBoot + OAuth2 + Redis + MongoDB:八年 Java 开发教你做 “安全不泄露、权限不越界” 的 SaaS 多租户平台
java·后端