laravel 11任务调度异常处理核心是解耦调度与异常响应:命令handle()需主动抛异常,通过withexceptions统一捕获并记录,启用failed_jobs表支持重试,配合日志监控实现可观测性。

Laravel 11 的任务调度异常处理,核心在于把调度逻辑和异常响应解耦——调度本身不直接捕获异常,而是靠命令执行阶段的错误传播 + 全局异常处理机制协同工作。关键不是“在 schedule() 里 try-catch”,而是让失败的任务能被记录、告警、重试或人工干预。
调度命令自身要主动抛出异常
自定义 Artisan 命令(如 app/Console/Commands/SendDailyReport.php)的 handle() 方法中,遇到业务错误应明确 throw 异常,而不是静默返回或 return false:
- 数据库操作失败、第三方 API 调用超时、文件生成失败等,都该抛出具体异常类(如
SendReportFailedException) - 避免在
handle()中吞掉异常(比如空catch),否则调度器会认为任务“成功完成”,实际却没做事 - 可设置
public $timeout = 120;防止卡死,超时会自动抛出Symfony\Component\Process\Exception\ProcessTimedOutException
利用 Laravel 11 的 withExceptions 统一拦截
异常不会在调度器层面消失,最终会进入全局异常处理流程。Laravel 11 推荐在 bootstrap/app.php 的 withExceptions() 中注册处理逻辑,而非修改 App\Exceptions\Handler:
- 对调度相关的异常(如
RuntimeException、ConnectionException、自定义TaskFailedException),用report()记录带上下文的日志 - 示例:记录任务名、运行时间、输入参数(注意脱敏)、服务器主机名
- 若需告警(如钉钉/企业微信),在
report()回调中触发,但加->stop()避免重复上报
启用失败任务表并定期巡检
调度命令本质是通过 php artisan schedule:run 触发的 Artisan 命令,它走的是 Laravel 的命令执行管道,因此天然支持队列失败机制(即使没用 Redis 队列):
- 运行
php artisan queue:failed-table && php artisan migrate创建failed_jobs表 - 当命令抛出未捕获异常,且该命令被标记为可失败(默认即支持),就会写入此表
- 用
php artisan queue:failed查看失败记录,php artisan queue:retry [id]手动重试 - 配合
schedule:list核对预期执行时间与实际失败时间,快速定位是否是定时偏差或环境问题
补充:日志与监控联动
光有异常捕获不够,得让问题“看得见”:
- 在命令
handle()开头加Log::info('Daily report started');,结尾加Log::info('Daily report finished');,形成执行链路 - 配置
logging.php将schedule通道单独输出到storage/logs/schedule.log,方便 grep 过滤 - 若已接入 Horizon,可在
horizon.php中为调度命令打标签(如'tags' => ['schedule']),在仪表盘中筛选查看











