Laravel Middlewareで例外をキャッチしてSlack通知する方法

こんにちは、さるまりんです🐒

本番環境で例外発生、気がつくのが遅れて大惨事!考えただけで怖いですし、こんなの経験したことある人多いんじゃないですか?僕ももちろんその一人です。

今回は、LaravelのMiddlewareを使ってアプリ全体の例外をキャッチし、Slackなどに通知する仕組みを試してみます。

なんでMiddlewareで?

Laravelには標準でException Handlerもありますが、ミドルウェアでも例外をキャッチして通知処理を追加することができます。アプリケーション全体のリクエストを横断的に監視したい、通知の前に情報を狭めて、条件によって通知する内容や手段を変えることもできるので、これがミドルウェアで実装しています。

1. Middlewareの作成

次のコマンドでミドルウェアを作成します。

php artisan make:middleware ExceptionNotifier

2. ミドルウェアのコードを編集

実際のコードを書きます。

app/Http/Middleware/ExceptionNotifier.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Throwable;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;

class ExceptionNotifier
{
    public function handle(Request $request, Closure $next)
    {
        try {
            return $next($request);
        } catch (Throwable $e) {
            // Slackで通知(Webhook URLは.envに記述します)
            $this->notifySlack($e, $request);
            throw $e; // そのまま投げます
        }
    }

    protected function notifySlack(Throwable $e, Request $request): void
    {
        // 通知機能のオンにするかどうかは設定で
        if (!config('app.exception_notify', false)) {
            return;
        }

        $message = "*Exception Occurred:*" .
            "URL: " . $request->fullUrl() . "\n" .
            "Message: " . $e->getMessage() . "\n" .
            "File: " . $e->getFile() . ":" . $e->getLine();

        Http::post(config('app.slack_webhook_url'), [
            'text' => $message
        ]);
    }
}

3. .envに設定を追加

config()で機能を使うかどうかと、Slackのwebhook URLの設定を追加します。

EXCEPTION_NOTIFY=true
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXXXXX/XXXXXX

4. config/app.phpにキーを追加

.envに追加した設定のキーをconfig/app.phpに追加して読めるようにします。

5. ミドルウェアの登録

app/Http/Kernel.phpにミドルウェアを登録します。

protected $middleware = [
    // 既存のグローバルミドルウェア
    ...
    // 新しく作るミドルウェアを追加します。
    \App\Http\Middleware\ExceptionNotifier::class,
];

補足です

Slack以外にもMail::to(...)->send(...)でメール通知にすることもできます。
また、開発環境や特定ルートでは通知しないようにしたい場合はこんな感じです。
↓は環境がlocal、もしくはhealthcheckへのアクセスの際は通知を飛ばさないようにしています。

if (app()->environment('local') || $request->is('healthcheck')) {
    return;
}

本番環境で起こる恐ろしいことはずっと気になってしまいます。
良い睡眠のためにも良いプログラミングが必要。
安心安全、できるだけ早く対応できるように備えていれば。
いやいや、起きないのがどちらかと言えば嬉しいですけど。

読んでくださってありがとうございました。

それではまた!