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

前言

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

一、错误不是"凭空冒出来"的,这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失败"),必须处理。

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

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

相关推荐
失因2 小时前
Nginx 特性、配置与实战部署
运维·数据库·nginx
魔芋红茶2 小时前
RuoYi 学习笔记 3:二次开发
java·笔记·学习
杨杨杨大侠2 小时前
Atlas Mapper 教程系列 (8/10):性能优化与最佳实践
java·spring boot·spring·性能优化·架构·系统架构
yinke小琪2 小时前
线程池七宗罪:你以为的优化其实是在埋雷
java·后端·面试
-雷阵雨-2 小时前
数据结构——包装类&&泛型
java·开发语言·数据结构·intellij-idea
我不是混子2 小时前
Spring Boot启动时的小助手:ApplicationRunner和CommandLineRunner
java·后端
惜鸟2 小时前
Java异常处理设计
java
渣哥2 小时前
从 IOC 到多线程:Spring 单例 Bean 的并发安全性全解析
java
程序员果子2 小时前
Kafka 深度剖析:架构演进、核心概念与设计精髓
大数据·运维·分布式·中间件·架构·kafka