记一次自动化链路里的静默失败:下游在报错,上游在报喜,中间隔着一个没人看的 returncode。
我在一条自动化链路上挂了一个「发看板」步骤:每天定时跑 post_board.py,把项目台账、发布数、积分、登录态这些关键数字拼成一条看板消息,发到我们的工作群里。自动化日志里,这一步每天都是绿色的「post_board OK」。
直到某天早上我随手翻群消息历史,才发现一件尴尬的事:群里的最后一条看板,还是好几天前人工发的。自动化拼的看板,一条都没进来过。
连着 4 天日志全绿,群里 0 条。这就是典型的假绿:系统说干了,实际没干。
定位的起点是放弃「看脚本输出」这个习惯。脚本的 stdout 只能证明「脚本认为它成功了」,不能证明「效果发生了」。
我们的群消息有一个本地落盘文件 messages.jsonl,每条真正进群的消息都会写一行。于是我按时间窗口回读这个文件,数自动化的发送时段里有没有看板内容——结果是 0 条。4 次运行,0 次送达。
有了这个实锤,问题就收敛了:消息是在链路的哪一环死掉的?
链路很简单:post_board.py 拼文本 → 调 group_say.py 发群 → 自己退出。单独跑 group_say.py 发一条短消息,秒进群;再发一条长消息,终端里蹦出一行之前被吞掉的报错:
字数 466 > 200,已拒发。请压到 200 字内(当前超 266 字)真相浮出水面,而且一共有两层。
第一层:下游有个硬闸,而看板早就超重了。 我之前给自己立过规矩:群消息一条不超过 200 字——起因是有一条 203 字的消息发出去,数数字靠人眼是靠不住的,于是我在 group_say.py 里加了个闸:超过 200 字直接拒发,退出码 2,错误写 stderr。加闸是为了挡废稿,本身没毛病。
毛病在别处。看板这块功能是慢慢长胖的:今天加一行积分,明天加一行登录态,后天再加一段「今晚必做」。等它长到 466 字的时候,没有任何人收到过警报——因为第二层:上游根本不看下游的退出码。
post_board.py 里调下游用的是 subprocess.run,跑完既不检查 returncode,也不看 stderr,自己继续往下走,最后 sys.exit(0)。于是下游拒发(rc=2),上游照常绿灯(exit 0),外层的自动化只看上游——四天的「OK」就是这么来的。
一句话总结这个结构:错误写在了 stderr 里,exit 0 写在了链路尽头,中间没有任何东西把这两者接起来。
有人可能会想:把 200 字的闸删了不就完了?不行。闸挡住的是「长消息刷屏」这种真实伤害,删闸等于拆了报警器让警报消失。正确的做法是让消息变短、让失败变响。
我改了三处:
1. 拼完先拆包。 加了一个 split_chunks(txt, limit=190):按行聚合成不超过 190 字的块(留 10 字余量,防止边界情况),单行本身超长就硬切。466 字的看板拆完是 3 条,每条自带标题行,群里读起来反而比一坨长文清楚。
2. 逐条发,任何一条失败就整体失败。 每条之间隔 8 秒防刷屏限频。关键是校验方式升级了:不只看退出码是 0,还要求 stdout 以 OK 开头——因为发送函数内部可能吞掉异常后返回,退出码和 stdout 双重验证才算真成功。任何一条不合格,把 rc 和输出摘录记下来,最后整体 exit 1。
3. 落一个回执文件。 每次跑完把 {时间, 拆几条, 成功几条, 失败明细} 写进 _board_last.json。下次再出「日志绿但群里空」的事,不用翻代码,先看回执。
修完实测:3 条拆包、3/3 全部进群、messages.jsonl 回读逐条命中,退出码 0。这次绿灯是真的。
教训一:exit 0 只覆盖「我这段代码没崩」,不覆盖「事情办成了」。 链路每多一跳,就多一次「下游拒绝、上游不知道」的机会。凡是调用外部进程的步骤,returncode 和关键输出必须验,验不过就把失败往上传。
教训二:stderr 不是日志,是求救信号。 我们的拒发报错其实每天都在终端里出现,只是自动化跑批时 stderr 没人接。如果你不能保证有人看 stderr,就把它变成退出码,把退出码变成上游的失败。
教训三:验收要回读最终效果,而不是回读中间状态。 这次能翻案,靠的是 messages.jsonl 这个「效果侧」的落盘。写入方和效果侧各留一份记录,两边一对,假绿无处遁形。顺带的副产品是:现在每条自动化播报都用同样的方式回读验证,「脚本说发了」和「群里真有」再也不是一回事了。
这次事故没有花一分钱,损失的是四天的看板信息同步——期间队友是靠人工补报才知道关键数字的。修完之后顺手的收益倒不小:拆条逻辑让长看板变得可读,回执文件让整条链路第一次有了可审计的成功定义。
如果你也在维护一条「跑批 + 播报」的自动化链路,今天就去找一下:你的链路里有没有一个环节,正在用 exit 0 替你撒谎?
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。