【入門編】CrashlyticsのWebhooksとAWS Lambdaを連携させた、高度なカスタムエラー自動チケット起票システム – 運用監視・オブザーバビリティ活用バイブル

こんにちは!日々のアプリ運用のメンテ、本当にお疲れ様です。

朝出社してSlackを開いた瞬間、Crashlyticsのアラートが何十件も流れていて絶望した経験はありませんか?「また同じエラーの通知だ……」「どのバージョンで発生しているんだっけ?」「これ、誰がチケット切るんだっけ?」――そんなルーティンワークに疲弊しているエンジニアの皆さんへ。

今回は、標準のSlack連携を卒業し、「CrashlyticsのWebhooks × AWS Lambda × 課題管理ツール(JiraやBacklog)」を組み合わせて、ノイズゼロのスマートな自動チケット起票パイプラインを作る方法を伝授します。

これをマスターすれば、開発チームの手間が劇的に減り、本当に直すべきクリティカルなバグに集中できるようになりますよ。一緒に一歩ずつ進めていきましょう!

—

1. なぜ標準のSlack連携では物足りないのか?

Firebase Crashlyticsの標準機能には、SlackやGoogle Chatへの通知機能が備わっています。「お、これで十分じゃないか」と思いがちですが、現場の規模が大きくなるにつれて、以下の壁にぶつかります。

1. ノイズの嵐: 発生するすべてのエラーが流れるため、Slackが通知の埋め立て地に。誰も読まなくなる。
2. アクションの欠如: 「あ、エラー起きてるね」で終わり、結局誰もJiraやBacklogにチケットを起こさない(または手動でコピペする地獄)。
3. 情報の不一致: 「どのリリースバージョンで」「どの端末(OS)で起きているか」を深掘りするために、わざわざFirebase Consoleを開き直す必要がある。

今回構築するシステムは、「特定の条件(例:非Fatalエラーでも特定回数を超えたもの、あるいは致命的なFatalエラー)だけをフィルタリングし、必要なコンテキストをすべて詰め込んだ美しいチケットを、自動でプロジェクト管理ツールに起票する」というものです。

—

2. 全体アーキテクチャの把握

私たちがこれから作るパイプラインの流れは非常にシンプルです。

[モバイルアプリ (iOS/Android)]
↓ クラッシュ発生!
[Firebase Crashlytics]
↓ Webhook (JSON) 送信
[API Gateway + AWS Lambda]
↓ ペイロード解析・フィルタリング
[Jira / Backlog REST API]
↓ 自動でチケット作成!
[Slack (オプションで綺麗に通知)]

この構成の美しいところは、サーバレス(AWS Lambda)なので運用コストがほぼゼロ、かつ自由にロジック(重複チケットの防止や担当者の自動アサインなど)をプログラミングできる点にあります。

—

3. 実装ステップ 1:AWS Lambda関数の準備

まずは、CrashlyticsからのWebhookを受け取り、パースしてチケット起票APIを叩く心臓部を作ります。今回はNode.js(TypeScriptではなく、初心者にも読みやすいようにプレーンなJavaScript)を採用します。

以下のコードをAWS Lambda(Node.js 18.x以上推奨)に配置してください。

// index.js
const https = require(‘https’);

// 環境変数から取得する設定(AWS Lambdaの環境変数に登録してください)
const JIRA_DOMAIN = process.env.JIRA_DOMAIN; // 例: “your-company.atlassian.net”
const JIRA_USER = process.env.JIRA_USER; // 例: “bot@your-company.com”
const JIRA_API_TOKEN = process.env.JIRA_API_TOKEN;
const JIRA_PROJECT_KEY = process.env.JIRA_PROJECT_KEY; // 例: “MOB”

exports.handler = async (event) => {
console.log(“Received event:”, JSON.stringify(event, null, 2));

try {
// 1. Crashlyticsからのペイロードをパース
const body = JSON.parse(event.body);

// CrashlyticsのWebhookペイロード構造(Alertタイプなど)を確認
const alertType = body.payload.alert_type;
const issueTitle = body.payload.title;
const issueSubtitle = body.payload.subtitle;
const appName = body.payload.app_name;
const displayVersion = body.payload.app_info.display_version;
const issuesURL = body.payload.url;

// 2. フィルタリングロジック(例: 新規発生したクラッシュ、かつFatalなものだけを通す)
if (alertType !== ‘new_fatal_issue’) {
console.log(`Skipped: Alert type ‘${alertType}’ is not a target.`);
return { statusCode: 200, body: “Skipped successfully” };
}

// 3. Jiraに起票するチケットのペイロードを作成
const jiraPayload = JSON.stringify({
fields: {
project: {
key: JIRA_PROJECT_KEY
},
summary: `[Crashlytics] ${appName} (${displayVersion}): ${issueTitle}`,
description: {
type: “doc”,
version: 1,
content: [
{
type: “paragraph”,
content: [
{
type: “text”,
text: `モバイルアプリで新しい致命的クラッシュが検知されました。\n\n詳細:\n- サブタイトル: ${issueSubtitle}\n- バージョン: ${displayVersion}\n- Firebase Console: ${issuesURL}`
}
]
}
]
},
issuetype: {
name: “Bug”
},
priority: {
name: “High”
}
}
});

// 4. Jira APIを叩く
await sendToJira(jiraPayload);

return {
statusCode: 200,
body: JSON.stringify({ message: “Ticket created successfully!” }),
};

} catch (error) {
console.error(“Error processing webhook:”, error);
return {
statusCode: 500,
body: JSON.stringify({ error: error.message }),
};
}
};

// Jira APIへのリクエストヘルパー関数
function sendToJira(data) {
return new Promise((resolve, reject) => {
const auth = Buffer.from(`${JIRA_USER}:${JIRA_API_TOKEN}`).toString(‘base64’);

const options = {
hostname: JIRA_DOMAIN,
path: ‘/rest/api/3/issue’,
method: ‘POST’,
headers: {
‘Authorization’: `Basic ${auth}`,
‘Content-Type’: ‘application/json’,
‘Content-Length’: Buffer.byteLength(data)
}
};

const req = https.request(options, (res) => {
let responseBody = ”;
res.on(‘data’, (chunk) => responseBody += chunk);
res.on(‘end’, () => {
if (res.statusCode >= 200 && res.statusCode < 300) { resolve(responseBody); } else { reject(new Error(`Jira API Error: ${res.statusCode} - ${responseBody}`)); } }); }); req.on('error', (error) => reject(error));
req.write(data);
req.end();
});
}

—

4. 実装ステップ 2:API Gatewayの設定

Lambda関数ができたら、それをインターネットから呼び出せるように Amazon API Gateway をフロントに置きます。

1. API Gatewayコンソールを開き、HTTP API(またはREST API)を新規作成します。
2. 統合先として、先ほど作成したAWS Lambda関数を指定します。
3. ルートパス(例: `/crashlytics-webhook`)を設定し、発行されたInvoke URL(エンドポイントURL)を控えておきます。

—

5. 実装ステップ 3:Firebase ConsoleでのWebhook設定

いよいよ最後の仕上げです。FirebaseとLambdaを繋ぎ込みます。

1. Firebase Consoleを開き、対象のプロジェクトを選択します。
2. 左メニューの [Crashlytics](または [Project Settings] > [Integrations])に移動します。
3. 設定アイコン(歯車マーク)から [アラートの設定 (Manage integrations)] を開きます。
4. [Webhook] の項目を有効にし、先ほどAPI Gatewayで発行したURLを貼り付けます。
5. 「テスト通知を送信 (Send test alert)」をクリックして、AWS Lambdaのログ(CloudWatch Logs)にデータが届くか確認しましょう!

—

6. 精度高いHelloWorld(動作確認)のコツ

いきなり本番アプリでクラッシュを起こすのは少しドキドキしますよね。確実に動作確認(HelloWorld)を行うためのステップはこちらです。

1. フェイクJSONの投函:
Postmanや`curl`コマンドを使い、Firebaseが送ってくるものと全く同じJSON構造を、直接API Gateway(Lambda)に向けてPOSTしてみます。

curl -X POST “YOUR_API_GATEWAY_URL” \
-H “Content-Type: application/json” \
-d ‘{“payload”: {“alert_type”: “new_fatal_issue”, “title”: “Test Crash”, “subtitle”: “NullPointerException”, “app_name”: “TestApp”, “app_info”: {“display_version”: “1.0.0”}, “url”: “https://console.firebase.google.com”}}’

2. チケットの確認:
Jira(またはBacklog)のボードを開き、綺麗に「[Crashlytics] TestApp (1.0.0)…」というバグチケットが自動起票されていれば大成功です!

—

先輩エンジニアからのアドバイス:さらに運用を洗練させるために

このシステムを導入すると、毎朝の絶望的なSlack通知から解放されるだけでなく、「誰がどのエラーに対応するか」が明確になります。

さらに慣れてきたら、以下の応用編にトライしてみてください:

  • 重複チケットの防止: すでに同じエラータイトル(フィンガープリント)のオープンチケットが存在する場合、新規作成する代わりに既存チケットにコメントを追記するロジックを入れる。
  • 担当者の自動アサイン: クラッシュが発生したパッケージ名(コンポーネント)ごとに、担当チームのJiraアカウントへ自動割り当てする。

これをマスターすれば、あなたのチームのオブザーバビリティ(可観測性)は一段上のステージに引き上げられます。日々の運用を自動化し、クリエイティブな開発に使える時間を増やしていきましょう!応援しています!

タイトルとURLをコピーしました