定时任务的错误处理:静默失败最可怕
凌晨 3:00 的 cron 已执行,但早上 9:00 目标文件仍未生成,这类“静默失败”比直接报错更难处理。
定时任务和手动执行不是同一个环境:路径可能不同,权限可能不同,环境变量可能缺失,网络也可能超时。终端里能跑通,不代表定时执行也能跑通。
真正要防的,不是报错,是“看起来没问题”
常见的静默失败包括:
- 命令失败了,但脚本继续执行
- 输出被重定向后无人查看
- 请求超时了,但脚本仍返回 0
- 新结果没写成功,却保留了旧文件
定时任务至少要做到三件事:
- 有退出码
- 有日志
- 有通知或可检查状态
第一层:脚本先学会正确失败
Shell 脚本通常先补这几行:
1 | |
作用分别是:
set -e:命令失败就退出set -u:使用未定义变量时退出pipefail:管道中前序命令失败时整体失败2>&1:把错误输出也写入日志
重点不是“记录过程”,而是失败时不要继续往下执行。
如果是 Python,也要保证异常会导致非 0 退出码:
1 | |
调度器判断任务是否成功,首先看的就是退出码。
第二层:日志别只记“开始了”
只有一句日志时,排查价值很低:
1 | |
更有用的日志应至少包含:
- 任务名
- 开始时间、结束时间
- 处理数量
- 关键输入
- 失败步骤
- 退出码
例如:
1 | |
如果 rows=128 这类数字来自示例,实际效果仍需按业务场景验证。
日志不要只输出到终端。定时任务通常无人值守,日志应落文件或进入系统日志。Linux 下可以直接用:
1 | |
第三层:给失败留一个最小通知
只有日志还不够,因为没人保证会主动查看。
最低成本的方式,是失败时发送通知。通知渠道可以是邮件、Webhook 或消息机器人,重点是失败后能被看到。
示例:
1 | |
这里的 trap ERR 表示:脚本中任一步骤出错,就触发通知。
如果不想在每次失败时立刻发消息,也可以做“心跳”:任务成功后更新时间戳,由监控检查是否超时未更新。这种方式对“脚本卡住但未退出”的情况更有用。
第四层:别直接覆盖结果
静默失败还常见于输出文件被部分写入,结果已损坏,但文件名看起来正常。
更稳妥的做法是先写临时文件,验证通过后再替换:
1 | |
这样即使中途失败,也不会直接破坏旧结果。
第五层:把运行环境写死一点
定时任务的高频问题之一,是环境不一致。
常见处理方式:
- 命令使用绝对路径
- 脚本开头显式
cd到工作目录 - 必要环境变量在脚本中写明
- 固定依赖版本,不依赖系统里“刚好有”
例如:
1 | |
调度器只负责按时执行,不会补全你平时手动运行时的上下文。
一个够用的检查清单
改造定时任务时,先检查这 6 项:
- 失败时是否返回非 0
- 标准输出和错误输出是否落日志
- 日志里是否有关键参数和处理结果
- 失败后是否有通知
- 输出是否先写临时文件再替换
- 路径、目录、环境变量是否显式指定
如果你准备补一项,优先补“失败告警入口”。至少要做到任务出错后,能明确知道是谁、通过什么渠道、在什么时候收到通知。
定时任务的错误处理:静默失败最可怕
https://ghost.kasumi.live/2026/03/20/定时任务的错误处理:静默失败最可怕/