Cloud Functions を使い App Engine のログを Slack に通知する

Cloud Functions を使い App Engine のログを Slack に通知する

GCP

投稿日:2018/03/01

こんにちは。株式会社トップゲートのりんご( @mstssk )です。
従業員向けのちょっとしたアプリを Google App Engine で作り、社内で利用中です。
問題になるのは運用時の監視です。
Google Cloud Platform では Stackdriver という強力なモニタリングツールを使用でき、 Slack に通知する機能もあります。
しかし、あくまで事前に決めたアラートポリシーに引っかかった事を通知してくれる機能のため、通知内容をカスタマイズできません。
そこで Cloud Functions を使い Slack に通知する仕組みを自前で作ってみました。
※本記事を執筆している2018年2月時点で Cloud Functions はベータ版です。今後の更新で動作が大きく変わる可能性があります。

構成

全体の構成は次のとおりです。
構成

  1. Cloud Logging で通知したいログのフィルタ条件を設定し、 Cloud Pub/Sub のトピックにエクスポート
  2. Cloud Pub/Sub がログを Cloud Functions に Push する
  3. Cloud Functions がログを Webhook で Slack へ投稿する

環境構築・デプロイ

Cloud Logging から Cloud Pub/Sub へログをエクスポート

まず、 GCP コンソールの Cloud Logging の画面で通知したいログの条件を入力します。
今回は HTTP ステータスコードが 500 になったリクエストをフィルタ条件としてみました。
出力対象を GAE アプリケーションrequest_log にした上で、フィルタ条件欄に status:500 と入力します。
Cloud Logging画面
次に「エクスポートを作成」ボタンを押下し、エクスポートの編集メニューを表示させます。

  • シンク名: gae-request-error
    • この名前はあくまで例です。フィルタ条件に応じて、後からわかりやすい名前をつけましょう。
  • シンクサービス: Cloud Pub/Sub
  • シンクのエクスポート先: 新しい Cloud Pub/Sub トピックを作成 を選択し、シンク名と同様に名前をつけてトピックを作成しましょう。
    • ここではエクスポートのシンク名と同じ名前にしておきました。

この後、「シンクを作成」を押せば Cloud Logging から Cloud Pub/Sub へログをエクスポートする設定は完了です。
Cloud Loggingエクスポート設定

Slack の Webhook URL を発行

Cloud Functions から Slack へ投稿するために Webhook を設定して Webhook URL を発行します。
Webhook は、発行した URL に POST リクエストするだけで指定した Slack チャンネルへ投稿できる機能です。
Webhook の設定は Slack 公式で日本語のガイドを用意しているので、そちらをご覧ください。

Webhook の設定画面で、投稿するチャンネル・投稿時の名前とアイコンなどを設定します。
そして、ここで表示されている Webhook URL を Cloud Functions で使用します。
Webhook設定

Cloud Functions を作成

GCP コンソールの Cloud Functions の画面から操作していきます。
「関数を作成」ボタンを押して、 Cloud Functions の作成画面へ進みましょう。
関数を作成
代わりに「 API を有効にする」というボタンが表示されている場合もあります。「 API を有効にする」ボタンを押して、 Cloud Functions を有効にすると「関数を作成」ボタンが表示されます。
関数の作成画面で必要な情報を入力していきましょう。

  • 名前: log2slack
    • 英数記号で任意の名前を入力します。ここでは実行する関数名と同じにしました。
  • 割り当てられるメモリ: 128 MB
  • トリガー: Cloud Pub/Sub トピック
  • トピック: gae-request-error
    • ログのエクスポート先と同じ Cloud Pub/Sub トピックを選択しましょう。
  • ソースコード: インラインエディタ
    • ※ package.json と index.js は後述
  • 実行する関数: log2slack

関数の作成
package.json と index.js は次のとおりです。
index.js の SLACK_WEBHOOK_URL の値は発行した Webhook URL に置き換えてください。

{
  "name": "log2slack",
  "version": "0.0.1",
  "dependencies": {
    "@slack/client": "^3.15.0"
  }
}
const { IncomingWebhook } = require("@slack/client");
const LOG_COLORS = {
    DEBUG: "#4175e1",
    INFO: "#76a9fa",
    WARNING: "warning",
    ERROR: "danger",
    CRITICAL: "#ff0000",
};
const SLACK_WEBHOOK_URL = "<実際のWebhook URLに置き換える>";
exports.log2slack = (event, callback) => {
    const data = JSON.parse(new Buffer(event.data.data, "base64").toString());
    const payload = data.protoPayload;
    const appId = payload.appId.slice(payload.appId.indexOf("~") + 1);
    const logUrl = `https://console.developers.google.com/logs?project=${appId}`
        + `&service=appengine.googleapis.com&logName=appengine.googleapis.com%2Frequest_log&expandAll=true`
        + `&filters=request_id:${payload.requestId}`;
    const body = {
        attachments: [{
            title: `*${payload.method}* ${payload.resource} <${logUrl}|Open Logging>`,
            text: (payload.line || []).map(l => `\`${l.time}\` \`${l.severity}\` ${l.logMessage}`).join("\n"),
            color: LOG_COLORS[data.severity],
            fields: [
                { title: "Severity", value: data.severity, short: true },
                { title: "HTTP Status", value: payload.status, short: true },
                { title: "Timestamp", value: data.timestamp, short: true },
                { title: "Environment", value: `${appId} ${payload.versionId}`, short: true },
            ]
        }]
    };
    const webhook = new IncomingWebhook(SLACK_WEBHOOK_URL);
    webhook.send(body, (err, res) => {
        if (err) { console.error(err); }
        if (res) { console.log(res); }
        callback();
    });
};

すべて入力し終えたら画面の下の方にある「作成」ボタンを押しましょう。
作成が完了したら、 Cloud Functions の一覧画面で次のように表示されているはずです。
関数の一覧
Cloud Pub/Sub のトピックから Push を受けるにはサブスクリプションを作成する必要がありますが、関数の作成時に Cloud Pub/Sub のトピックをトリガーに指定すると同時に作成してくれます。
サブスクリプション
これで App Engine のエラーログを Slack に通知できるようになりました!

Slack への出力例

実際に Slack へは次のように投稿されます。
Slackへの出力例
このログは動作確認用に App Engine で次の様に実装してアクセスしてみた時のものです。

http.HandleFunc("/api/dummy", func(w http.ResponseWriter, r *http.Request) {
    c := appengine.NewContext(r)
    log.Errorf(c, "エラーログ文字列")
    http.Error(w, "error!", http.StatusInternalServerError)
})

おわりに

私は Cloud Functions を初めて使ったのですが、この記事のとおりちょっとした機能が簡単に実装できてしまいました。
あくまでまだベータ版(2018年2月時点)の機能なので、正式リリースが待ち遠しいですね。
今回の Cloud Functions のコードは App Engine のログを前提にしていますが、書き換えれば他の GCP のサービスのログでも使用できます。
ただし、その場合は Cloud Functions のログをまた Cloud Functions で処理して…という無限ループにならないように気をつけてください。
Cloud Functions については本ブログの GCP 入門連載でも触れていますので、こちらの記事もどうぞご覧ください。

GCP のメリットを最大限に活用しよう!

GCP・G Suite のご相談・
お見積り依頼はお気軽に
TEL.03-5840-8815
お問合せフォーム TEL.03-5840-8815