PHP项目Laravel调度回调异常捕获

wen PHP项目 4

本文目录导读:

PHP项目Laravel调度回调异常捕获

  1. 基础异常处理
  2. 使用 onFailure 方法(Laravel 8+)
  3. 使用 withoutOverlappingonSuccess
  4. 创建自定义异常处理器
  5. 使用 Laravel 内置的 ping 功能
  6. 综合示例:完善的异常处理
  7. 全局异常处理器
  8. 最佳实践建议
  9. 添加监控和警告
  10. 关键要点:

在Laravel中处理调度回调异常,有几种有效的方法:

基础异常处理

在调度任务中直接捕获

// app/Console/Kernel.php
use Illuminate\Support\Facades\Log;
use Throwable;
protected function schedule(Schedule $schedule)
{
    $schedule->call(function () {
        try {
            // 你的定时任务逻辑
            $this->processData();
        } catch (Throwable $e) {
            Log::error('定时任务执行失败', [
                'message' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
                'trace' => $e->getTraceAsString()
            ]);
            // 可以发送邮件或通知
            // Notification::route('mail', 'admin@example.com')->notify(new TaskFailed($e));
        }
    })->everyMinute();
}

使用 onFailure 方法(Laravel 8+)

protected function schedule(Schedule $schedule)
{
    $schedule->call(function () {
        // 定时任务逻辑
    })
    ->everyMinute()
    ->onFailure(function (Throwable $e) {
        // 任务失败时的处理
        Log::critical('调度任务失败', [
            'exception' => $e->getMessage(),
            'time' => now()->toDateTimeString()
        ]);
        // 发送通知
        // 报警、邮件等
    });
}

使用 withoutOverlappingonSuccess

$schedule->call(function () {
    // 任务逻辑
})
->everyFiveMinutes()
->withoutOverlapping(10) // 防止任务重叠
->onSuccess(function () {
    Log::info('任务执行成功');
})
->onFailure(function (Throwable $e) {
    Log::error('任务执行失败: ' . $e->getMessage());
});

创建自定义异常处理器

创建自定义异常类

// app/Exceptions/ScheduleTaskException.php
namespace App\Exceptions;
use Exception;
class ScheduleTaskException extends Exception
{
    public function __construct($message = "", $code = 0, Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
}

创建调度任务基类

// app/Console/Commands/BaseScheduleCommand.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Throwable;
abstract class BaseScheduleCommand extends Command
{
    abstract protected function executeTask();
    public function handle()
    {
        $startTime = microtime(true);
        try {
            $result = $this->executeTask();
            $duration = round(microtime(true) - $startTime, 2);
            Log::info('调度任务成功', [
                'command' => static::class,
                'duration' => $duration . '秒'
            ]);
            return $result;
        } catch (Throwable $e) {
            Log::error('调度任务异常', [
                'command' => static::class,
                'message' => $e->getMessage(),
                'line' => $e->getLine(),
                'file' => $e->getFile()
            ]);
            $this->sendFailureNotification($e);
            return 1; // 返回非零值表示失败
        }
    }
    protected function sendFailureNotification(Throwable $e)
    {
        // 发送邮件、Slack等通知
        // Notification::route('mail', config('app.admin_email'))
        //     ->notify(new ScheduleTaskFailed($e));
    }
}

使用 Laravel 内置的 ping 功能

$schedule->call(function () {
    // 任务逻辑
})
->everyMinute()
->pingBefore('https://api.example.com/job/started')
->thenPing('https://api.example.com/job/finished')
->onFailure(function (Throwable $e) {
    // 发送错误通知
    Log::error('任务失败', $e->getMessage());
});

综合示例:完善的异常处理

// app/Console/Kernel.php
protected function schedule(Schedule $schedule)
{
    $schedule->call(function () {
        $job = new ProcessDataJob();
        $job->handle();
    })
    ->name('process-data')
    ->withoutOverlapping(30) // 防止重叠
    ->runInBackground() // 后台运行
    ->onSuccess(function () {
        Log::info('数据处理成功', ['time' => now()]);
    })
    ->onFailure(function (Throwable $e) {
        Log::error('数据处理失败', [
            'message' => $e->getMessage(),
            'time' => now()->toDateTimeString()
        ]);
        // 发送错误通知
        Notification::route('mail', config('mail.admin_address'))
            ->notify(new ScheduleTaskFailed($e));
        // 或者发送到钉钉/Slack群
        // Http::post(config('services.dingtalk.webhook'), [
        //     'msgtype' => 'text',
        //     'text' => [
        //         'content' => '调度任务失败: ' . $e->getMessage()
        //     ]
        // ]);
    });
    // 也可以设置重试
    $schedule->call(function () {
        try {
            // 任务逻辑
        } catch (Throwable $e) {
            // 如果失败,在5分钟后重试
            Log::warning('任务失败,准备重试', ['error' => $e->getMessage()]);
            throw $e;
        }
    })
    ->everyMinute()
    ->retry(3, 60); // 重试3次,间隔60秒
}

全局异常处理器

// app/Exceptions/Handler.php
public function register()
{
    $this->reportable(function (Throwable $e) {
        if ($e instanceof ScheduleTaskException) {
            Log::channel('schedule')->error('调度任务异常', [
                'exception' => $e
            ]);
            // 发送通知
            $this->notifyAdmin($e);
        }
    });
}
protected function notifyAdmin(Throwable $e)
{
    // 发送邮件/短信/微信通知
}

最佳实践建议

// 建议将调度任务拆分为独立的Job类
$schedule->job(new ProcessDataJob('param1', 'param2'))
    ->everyMinute()
    ->onFailure(function (Throwable $e) {
        // 记录失败日志
        Log::error('队列任务失败', ['message' => $e->getMessage()]);
        // 可以计算连续失败次数,达到阈值后发送告警
        cache()->increment('schedule:fail_count', 1);
        $failCount = cache()->get('schedule:fail_count', 0);
        if ($failCount >= 5) {
            // 发送严重告警
            alertAdmin("连续5次调度失败,最后一次错误: " . $e->getMessage());
            // 重置计数
            cache()->forget('schedule:fail_count');
        }
    });

添加监控和警告

use Carbon\Carbon;
protected function schedule(Schedule $schedule)
{
    $schedule->call(function () {
        $start = now();
        try {
            // 任务逻辑
            $duration = now()->diffInSeconds($start);
            // 如果执行时间过长,记录警告
            if ($duration > 300) {
                Log::warning('任务执行时间过长', ['duration' => $duration]);
            }
        } catch (Throwable $e) {
            Log::error('任务执行失败', [
                'message' => $e->getMessage(),
                'start' => $start->toDateTimeString(),
                'duration' => now()->diffInSeconds($start)
            ]);
            throw $e;
        }
    })
    ->everyMinute()
    ->pingBefore('https://example.com/ping')
    ->thenPing('https://example.com/pong');
}

关键要点:

  1. 错误日志:始终记录完整的异常信息,包括堆栈
  2. 通知机制:配置邮件、Slack等通知渠道
  3. 重试策略:合理设置重试次数和间隔
  4. 防止重叠:使用 withoutOverlapping 防止任务堆积
  5. 监控告警:持续监控任务健康状态
  6. 异常分类:区分业务异常和系统异常

这样就能全面捕获和处理调度任务的异常,确保系统的稳定性和可维护性。

抱歉,评论功能暂时关闭!