任务执行失败时需在handle()方法外层用try/catch捕获throwable,记录完整上下文并脱敏敏感字段;laravel horizon应监听horizon\events\jobfailed事件,结合redis滑动窗口计数防抖,按任务标识限频告警;自研调度器须用register_shutdown_function和set_error_handler兜底捕获致命错误。

任务执行失败时如何捕获异常并触发告警
PHP 任务调度框架(如 Laravel Horizon、Symfony Messenger 或自研基于 cron + supervisor 的调度器)本身不内置告警逻辑,失败后默认只记录日志或抛出异常。必须在任务执行入口主动包裹异常处理,否则告警链路会断掉。
关键点在于:不能只依赖框架的「重试机制」或「失败队列」,因为重试中也可能持续失败,而告警需在首次失败或连续失败阈值达成时立即触发。
- 在任务的
handle()方法最外层用try/catch捕获Throwable,而非仅Exception(避免Fatal Error漏掉) - 对特定错误类型(如网络超时、DB 连接中断)做白名单过滤,避免高频误报;但
ParseError、FatalError必须告警 - 捕获后记录完整上下文:
getTraceAsString()、getLine()、getFile()、任务类名与参数(注意脱敏password、token等字段)
Laravel Horizon 失败任务告警配置要点
Horizon 的 failed_jobs 表只是存储,php artisan horizon:supervisors 不会自动通知。要实现告警,必须接入其事件系统。
推荐方式是监听 Illuminate\Queue\Events\JobFailed 事件,但它在 Horizon 启用时可能被屏蔽——因为 Horizon 使用自己的 Horizon\Events\JobFailed。
- 在
EventServiceProvider中注册监听器:Horizon\Events\JobFailed::class - 监听器内判断
$event->job->getName()和$event->exception,避免对Horizon\Listeners\TrimFailedJobs这类内部任务告警 - 不要在监听器里调用阻塞操作(如同步 HTTP 请求),建议推送到轻量消息队列(如 Redis
LPUSH)再由独立 worker 发送通知 - 注意 Horizon 的
trim配置(trim.failed)可能导致失败记录被清空,告警逻辑必须在 trim 前触发
通知渠道选型与防抖策略
直接发邮件或钉钉机器人看似简单,但生产环境容易因瞬时失败风暴导致刷屏。必须加控制层。
核心不是「怎么发」,而是「什么条件下发」和「多久发一次」。
- 按任务标识(如
App\Jobs\SyncOrderJob)做失败计数,10 分钟内失败 ≥3 次才触发首条告警 - 使用 Redis 的
INCR+EXPIRE实现滑动窗口计数,键名建议为alarm:job_failed:<code>md5($jobClass) - 企业微信/钉钉通知需携带跳转链接,指向 Horizon UI 的具体失败详情页(URL 含
failed?search=xxx) - 禁用「失败即短信」策略——短信通道贵且不可控,仅对 P0 级任务(如支付回调补单)开启,并限制每小时最多 2 条
自研调度器中嵌入告警的最小可行结构
如果你没用 Laravel 或 Symfony,而是基于 pcntl_fork 或 systemd + php script.php 调度,告警必须从进程生命周期切入。
不能等脚本退出码非 0 再通知——因为 PHP Fatal 错误会导致进程直接终止,退出码不可靠。
- 主调度循环中用
register_shutdown_function()检查error_get_last(),若存在且type属于E_ERROR、E_PARSE、E_CORE_ERROR,立即触发告警 - 每个子任务执行前设置
set_error_handler(),将E_WARNING及以上转为异常,统一进try/catch - 记录失败时写入结构化日志(JSON 格式),包含
job_id、runtime_ms、memory_usage_kb,方便后续聚合分析 - 避免在告警函数中初始化新数据库连接——复用已有 PDO 实例或改用
file_put_contents写本地告警队列文件,由守护进程轮询发送
告警不是越快越好,而是越准越好。漏掉一次 P0 故障比多发十次低优先级告警更危险。真正难的不是代码怎么写,是定义清楚「哪些失败值得人看」「哪些该自动修复」「哪些应静默降级」——这些规则得写进文档,而不是藏在 if 里。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











