tsyama記

プログラミングとそのほか

Laravelで捕まえたExceptionをSlackに通知する

はじめに

こんにちは。tsyamaです。
今回はLaravelで捕まえたExceptionをSlackに通知します。

なぜこんな記事を書くのか

似たようなことをしている記事は公式を含めたくさんあるんですが、網羅的に説明されてるので情報量多すぎて🤔💦となります。なので今回はExceptionのSlack通知一点絞りで書き記しておきます。
あと、Notify周りのクラス構造がよくわからなかったりしたので、そのへんについては頑張ってわかりやすくまとめます。

登場人物

app/Exceptions/Handler.php

例外をつかさどる男。Laravel叩き上げクラス。今回はSlackNotifiableを携えたことにより、notify()が使えるようになる。

app/Traits/Notification/SlackNotifiable.php

持っているだけでnotify()が使えるようになる魔法のランプ。今回新しく作る。

app/Notifications/CaptureExceptionNotification.php

notify()発動のたびに現れてはSlackのwebhookを叩く魔人。今回新しく作る。

作業内容

CaptureExceptionNotificationの作成

まずは通知をつかさどる魔人を作ります。魔人はartisanで作成できます。

php artisan make:notification CaptureExceptionNotification

魔人は用途に応じて現れ、メール通知やSlack通知など様々な通知をこなしますが、今は一旦置いておきます。

Slack Webhook URLの取得

Slackに通知するためのWebhook URLを作成しておきます。これは魔人が使います。
Webhook URLの作り方はいろんなところに載ってるのでGoogle検索を活用するか、本記事の最後にある参考リンクを見てください。

Guzzle HTTP Clientのインストール

これは舞台裏の話なんですが、Laravelで提供するSlack通知のメソッドには必要なライブラリがあるのでComposerでインストールしておきます。

composer require guzzlehttp/guzzle

設定ファイルの整備

Slack通知で使う設定を設定ファイルに置いておきます。

config/slack.php

<?php

return [
    'channel' => env('SLACK_CHANNEL'),
    'webhook_url' => env('SLACK_WEBHOOK_URL'),
];

.env

SLACK_CHANNEL=#チャンネル名
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/...

CaptureExceptionNotificationの設定

魔人は生まれたばかりなのでSlackへの通知のやり方を知りません。ここで教え込みます。toSlack()というメソッドを実装すると魔人はSlackへの通知を覚えます。

    public function __construct(Exception $e)
    {
        $this->exception = $e;
    }

    public function via($notifiable)
    {
        return ['slack'];
    }

    public function toSlack($notifiable)
    {
        $exception = $this->exception;
        return (new SlackMessage)
            ->to(config('slack.channel'))
            ->error()
            ->content('魔人がエラーを検知しました')
            ->attachment(function ($attachment) use ($exception) {
                $attachment
                    ->title(get_class($exception))
                    ->content($exception->getMessage());
            });
    }

便宜上、魔人が生まれたとき(__construct())にExceptionを持たせるようにします。また、via()では通知チャンネルを配列で指定する必要があるので、slackという文字列を返してやります。

SlackNotifiableの作成

魔人がSlack通知をすることができるようにはなったものの、今度は魔人の呼び出し方について考えなくてはなりません。
今回はSlackNotifiableという魔法のランプをtraitの形で実装してみます。

app/Traits/Notification/SlackNotifiable.php

<?php
namespace App\Traits\Notification;

use Illuminate\Notifications\RoutesNotifications;

trait SlackNotifiable
{
    use RoutesNotifications;

    public function routeNotificationForSlack() :string
    {
        return config('slack.webhook_url');
    }
}

Traitsディレクトリがなければ新しく作成します。種明かしをすると、RoutesNotificationクラスをuseして、routeNotificationForSlack()という、Slack Webhook URLを文字列形式で返すメソッドさえあれば誰でも魔人を呼び出せますが、今回は取り扱いしやすいように魔法のランプという形をとっています。

これにより、魔法のランプをuseした者は魔人を呼び出すnotify()が使えるようになります。

エラー時にnotify()を実行する

あとはエラー時にnotify()を実行してExceptionを渡す人間が必要です。これはExceptionをつかさどるApp\Exceptions\Handlerさんに任せましょう。

SlackNotifiableをuseしてnotify()を使えるようにしたうえで、既存のreport()メソッドをこのようにします。

    public function report(Exception $exception)
    {
        if ($this->shouldReport($exception) && app()->environment('production')) {
            $this->notify(new CaptureExceptionNotification($exception));
        }
        parent::report($exception);
    }

report()はエラー発生時に呼び出されるメソッドらしいです。詳しくは参考記事がとってもわかりやすいです。

shouldReport()は引数にExceptionを渡して、こいつがreportすべきExceptionなのかを判定してくれます。Handlerさんのプロパティである$dontReportに指定された型、もしくはLaravelのコアで定義されている$internalDontReportに指定された型以外のExceptionであればreportに値すると判定されます。ちなみに$internalDontReportはこんな感じ。

    protected $internalDontReport = [
        AuthenticationException::class,
        AuthorizationException::class,
        HttpException::class,
        HttpResponseException::class,
        ModelNotFoundException::class,
        TokenMismatchException::class,
        ValidationException::class,
    ];

おそらくこれで完成です。

おわりに

わかりやすく記事を書こうとしたら、残暑も相まって思考がだいぶバグりました。

参考にしたもの

qiita.com

www.ritolab.com

larapet.hinaloe.net