Sentry の エラー通知を Chatwork へ投げたい

こんにちは、エンジニアの柿添です。

この夏は本当に暑いですね。
日光にあたらないように出勤時のルートを涼しいルートへ変更しました。
多少は効果があるかと思いますが、暑さにはやはり勝てません。

ダイエットは忘れて食欲の秋を待ちわびています。

今回のテーマは、Sentry のエラー通知を Chatwork へ投げる方法です。 この手法を使うことで、エラー発生時にリアルタイムでの対応が可能となります。

実際に稼働しているLaravelアプリケーションで、SentryとChatworkを連携させる方法を紹介いたします。

そもともSentryとは

Sentryはエラーモニタリングとデバッグの専門サービスです。
アプリケーションが稼働中に発生するエラーや問題をリアルタイムでキャッチし、その詳細情報を明確に報告します。
この機能によって、開発者はエラーの即時認識と、それに対する効率的な対応が可能になります。
さらに、エラーの発生傾向や根本原因を分析することで、アプリケーションの全体的な品質を高める助けともなります。

Sentry がサポートしているPlatforms

Sentryは多くのプログラミング言語とフレームワークをサポートしています。
主なものは以下の通りです。

  • JavaScript (React, Angular, Vue.js など)
  • Python (Django, Flask など)
  • Java (Android, Spring など)
  • Ruby (Ruby on Rails)
  • PHP (Laravel, Symfony など)
  • C# (.NET, Unity)
  • Go
  • Swift / Objective-C (iOS)
  • C/C++
  • Rust

Sentry の用語

Sentryを使う上で覚えておきたい用語をいくつか紹介します。
詳細はSentry公式ドキュメントを参照してください。

  • DSN (Data Source Name): Sentryにデータを送信するための認証情報が含まれたURL。
  • Event: アプリケーションで発生した特定のエラーまたは問題。
  • Issue: 一連の同じタイプのEventsをグループ化したもの。
  • Release: ソフトウェアの新しいバージョン。Releasesを設定すると、どのバージョンでエラーが発生したかを追跡できます。
  • Scope: イベントデータのコンテキストを設定するためのオブジェクト。
  • Tag: イベントに追加情報を付与するためのキーワード。
  • Alerts: Sentry内で設定するエラーまたはイベントに対する通知ルール。
  • Attachments: イベントに関連付けられる追加データファイル。
  • Data: Sentryに送信されるエラーやイベントの情報。
  • Environment: アプリケーションが動作している環境(例:開発、ステージング、本番など)。
  • Error: アプリケーションで発生した問題点、通常はEventとしてSentryに報告されます。
  • Performance Monitoring: アプリケーションのパフォーマンスをリアルタイムで監視する機能。
  • Project: Sentry内で特定のアプリケーションまたはサービスを管理するための単位。

Sentryのプラン

Sentryはさまざまなプランが用意されています。
無料のプランからエンタープライズプランまで、用途や規模に応じて選ぶことができます。
無料プランでも基本的なエラーモニタリング機能は十分に使用できますが、
高度な分析機能やサポートが必要な場合は、有料プランを検討する価値があります。

いつでもアップグレードまたはダウングレードでき、
次の請求サイクルの開始時に料金が自動的に調整されます。

詳細は Sentry Pricing を参照してください。

Sentry Developer 無料プラン

試しにSentry Developerの無料プランを使ってみます。 最大5,000イベント/月までの使用が可能な $0/月 の無料プランです。

  • プロジェクトごとに最大1名のユーザーを追加可能
  • すべての言語をサポート
  • リリース追跡
  • データは最大1GBまで

Sentry の Laravel パッケージをインストール

LaravelでSentryを利用するために、SentryのLaravelパッケージをインストールします。

composer require sentry/sentry-laravel

Sentry の.env設定

次に、.envファイルにSentryのDSNを設定します。

SENTRY_LARAVEL_DSN=your_dsn_here

Sentry 利用のための Handler を設定

また App/Exceptions/Handler.php の report メソッドに以下のコードを追加します。

public function report(Exception $exception)
{
    if (app()->bound('sentry') && $this->shouldReport($exception)) {
        app('sentry')->captureException($exception);
    }

    parent::report($exception);
}

Sentry の送信テスト

Laravelのアプリケーションでエラーを発生させ、Sentryに送信されるか確認します。

php artisan sentry:test

これでLaravel側の設定は完了です。

さらに詳しい内容についてはSentry公式ドキュメントを参照してください。

Sentry Alert の設定

Sentryでは、エラー発生時に通知を送るためのAlert機能があります。 この機能を使うことで、エラー発生時にリアルタイムで通知を受け取ることができます。

Sentry Alert の設定箇所

Sentry の Webhook を使う

SentryにはWebhook機能があり、エラー発生時に特定のURLに通知を送ることができます。 この機能を利用し、Cloudflare Workersを経由してChatwork APIを呼び出すことで、エラー情報をChatworkに送信します。

Alert -> Edit Alert Rule から Webhook を追加します。

Sentry WEBHOOKS の設定箇所

Cloudflare Workers で Chatwork へ通知する

Cloudflare Workersを使用して、SentryからのWebhookを受け取り、それをChatworkへリレーします。
JavaScriptで短いスクリプトを書くだけで、連携を実現できます。

Cloudflare Workers の設定

Cloudflare Workersを使用するには、Cloudflareのアカウントが必要です。
アカウントを持っていない場合は、こちらから登録してください。

Workers & Pagesを選択し、Workersを有効にします。
クイック編集を選択し、以下の環境変数を設定します。

Cloudflare workers の 変数設定
  • CHATWORK_API_TOKEN: Chatwork APIのトークン
  • CHATWORK_ROOM_ID: ChatworkのルームID

Chatwork APIのトークンは、こちらから取得できます。

Cloudflare Workers のコード

workerの名前は worker-relaying-alerts とします。
今回はwranglerを使わずに、Cloudflare Workersのコードを直接編集します。
(wranglerとは、Cloudflare Workersを開発するためのコマンドラインツールです。)

const toJapanTime = (timestamp) => {
  if (!timestamp) return "UnKnown";
  const date = new Date(timestamp * 1000);
  const offset = 9 * 60;
  const localTime = new Date(date.getTime() + offset * 60000);
  return localTime.toLocaleString("ja-JP");
};

const formatSentryData = (data) => {
  const eventData = data.event || {};

  return `
[info]Timestamp: ${toJapanTime(eventData.timestamp)}
Sentry URL: ${data.url || "UnKnown"}
[/info]

[code]- Project: ${data.project_name || "UnKnown"}
- Level: ${data.level || "UnKnown"}
- Culprit: ${data.culprit || "UnKnown"}
- Message: ${data.message || "UnKnown"}
- Platform: ${eventData.platform || "UnKnown"}
- Environment: ${eventData.environment || "UnKnown"}
- User: ${(eventData.user && eventData.user.email) || "UnKnown"} (${(eventData.user && eventData.user.ip_address) || "UnKnown"})
[/code]

Error Details:
--------------
${(eventData.logentry && eventData.logentry.formatted) || "UnKnown"}

Stacktrace:
-----------
${(eventData.stacktrace && eventData.stacktrace.frames) ? eventData.stacktrace.frames.map(frame => `${frame.filename}:${frame.lineno} in ${frame.function}`).join('\n') : "UnKnown"}

[hr]

Context:
[code]
${JSON.stringify(eventData.contexts || {}, null, 2)}

[/code]
  `;
};

const sendMessageToChatwork = async (env, message) => {
  const CHATWORK_URL = `https://api.chatwork.com/v2/rooms/${env.CHATWORK_ROOM_ID}/messages`;
  const CHATWORK_HEADERS = {
    'X-ChatWorkToken': env.CHATWORK_API_TOKEN,
    'Content-Type': 'application/x-www-form-urlencoded'
  };

  const body = `body=${encodeURIComponent(message)}`;

  return await fetch(CHATWORK_URL, {
    method: 'POST',
    headers: CHATWORK_HEADERS,
    body: body
  });
};

export default {
  async fetch(request, env, ctx) {
    if (request.method !== 'POST') {
      return new Response('Method not supported.', { status: 405 });
    }

    try {
      const requestData = await request.json();
      const message = formatSentryData(requestData);
      const chatworkMessage = `[info][title]Sentry Error Notification[/title]${message}[/info]`;

      const chatworkResponse = await sendMessageToChatwork(env, chatworkMessage);

      if (!chatworkResponse.ok) {
        throw new Error('Failed to send notification to Chatwork.');
      }

      return new Response('Notification sent to Chatwork.', { status: 200 });

    } catch (error) {
      await sendMessageToChatwork(env, `Webhook Error: ${error.message || error}`);
      return new Response(`Error: ${error}`, { status: 500 });
    }
  },
};

受け取り時のチェックは行っていないので、必要に応じて追加してください。
通知メッセージには、Sentryから送られてきたエラー情報を含めていますが、必要に応じて変更してください。

Sentryの管理画面へ戻り、 Project -> Alert Settings へ のCloudflare WorkersのURLを設定してください。

Sentry のアカウントを準備、Laravel での設定、Cloudflare Workers の設定、Cloudflare Workers のコードを設定したら、Sentry からの通知を Chatwork へ投げる準備は完了です。

test通知をChatworkで受け取った例

[info][title]Sentry Error Notification[/title]
[info]Timestamp: 2023/8/31 03:49:51
Sentry URL: XXXXXXXXXX
[/info]

[code]- Project: climber-test-php-laravel
- Level: error
- Culprit: raven.scripts.runner in main
- Message: This is an example Python exception
- Platform: python
- Environment: prod
- User: sentry@example.com (127.0.0.1)
[/code]

Error Details:
--------------
This is an example Python exception

Stacktrace:
-----------
raven/base.py:303 in build_msg
raven/base.py:459 in capture
raven/base.py:577 in captureMessage
raven/scripts/runner.py:77 in send_test_message
raven/scripts/runner.py:112 in main

[hr]

Context:
[code]
{
  "browser": {
    "name": "Chrome",
    "version": "28.0.1500",
    "type": "browser"
  },
  "client_os": {
    "name": "Windows",
    "version": "8",
    "type": "os"
  }
}

[/code]
  [/info]

まとめ

この記事で取り上げたテーマは、エラーモニタリングサービスであるSentryと、コミュニケーションプラットフォームであるChatworkを連携させる方法です。
特に、SentryのWebhook機能とCloudflare Workersを活用し、リアルタイムでエラー情報をChatworkに送信する設定に焦点を当てました。
この連携により、エラー発生時の対応がより迅速に、そして効率的に行えるようになります。

加えて、Sentryの基本的な用語やプラン、さらにはLaravelでの設定方法も簡単に触れました。
これらの知識を組み合わせることで、より効果的なエラーモニタリングと問題解決が可能となります。

この連携手法を是非とも試して、開発プロセスの効率化をはかってください。
そして、どの通知先が最も効果的か、どのような通知内容が必要かはプロジェクトやチームによって異なるため、是非とも自分たちに合った設定を行ってください。

それでは、より効率的な開発ライフをお送りください。

良いコーディングを!

Related article

おすすめ関連記事