Open9

Firebase Cloud FunctionsにおいてthrowされたErrorとcatchしたErrorをそれぞれSlack通知する

meijinmeijin

やりたいこと

Functionsのエラーログについて、Slack等で検知できるようにする

現状

Functionsのコンソールで見れるけど

  • 通知がない
  • 全部レベルがDebugなので、紛れる
  • UI的に使いにくい

方針

  • このままだと通知がないので、別のログにExportできないか?
  • Debugレベルのログばかりだけど、この情報レベルをコントロールできないか?実装をどのように変えるか?
meijinmeijin

参考文献

https://firebase.google.com/docs/functions/writing-and-viewing-logs

console.logの利用について

console.logを使ってもいいけど、長期的にはよくない?

console.log を使用していて、Node.js 8 ランタイムから Node.js 10 または 12 に移行すると、ログの読み取りに問題が生じることがあります。この問題を一時的に回避するには、次のライブラリを関数に追加します。

require("firebase-functions/lib/logger/compat");

Node.js 10 または 12 に移行する際に推奨される長期的なソリューションは、ロガー SDK を使用するようにコードをリファクタリングすることです。
meijinmeijin

現状わかっていること

console.logを使う

→Functionsのログに流れる。レベルは指定されない(ここがDocと違うので混乱)

console.warn/errorを使う

→これらもレベルは指定されない。謎

Functionsのログを他で見る方法

https://console.cloud.google.com/logs/
こちらで見れる
こっちのほうが見た目が綺麗。個人的には好み

throw new Errorする

https://console.cloud.google.com/errors
こちらで見れる
ログではなくてエラー報告。ロギングと、エラー報告の責務を分けることができそう。好感が持てる

meijinmeijin

これから調べること

Error Reportingから、Slack通知やメール通知ができるか?できるなら、プロダクトコードを改修してエラー時は素直にThrowする。独自例外を作っていく感じかな

Logsから、Slack通知やメール通知ができるか?この場合、大量のログからフィルタリングしないといけないのでちょっと嫌かな。コードを変更したとしても独自例外Throwのほうがよさそう

meijinmeijin

気が付きやすさを考えるとちょっと手間でもSlack対応するほうがよさそう

meijinmeijin

→Functionsのログに流れる。レベルは指定されない(ここがDocと違うので混乱)

これが割と致命的で、上記のSlack通知作戦を真似するのであればSeverityで区別できるのがベスト。

ロガー SDKとやらを使えば改善されるのか。試す

meijinmeijin

ロガーSDKを使っても改善されなかった。要はFirebase Functionsからコンソールに流したログはSeverityがつかないらしい。なんでやねん

ErrorをThrowする→GCPのロギングで見れる→上記の記事を使ってPubSub経由で再度Functionsに持ってきてSlack通知

これがよさそう。

meijinmeijin

まとめ

GCPのロギングにあるログルーターという機能を使って、Functionsの機能をPubSubに飛ばす
PubSubにさえあればそれをフックにFunctionsを動かすことができるので、Slack通知をやる

ログルーターの使い方

コンソールで適当にポチポチしたら使える。そもそもログエクスプローラーでクエリ言語の練習はできる。ので、そのクエリ言語をフィルタの条件に打ち込めばいい。

Errorがthrowされた場合のログルーター

あえてthrowしたときとか、全く捕捉されなかったとき。

このときはSeverityがERRORでロギングに出るので以下でフィルタリングできる。

resource.type="cloud_function"
resource.labels.project_id="XXXX"
severity>="ERROR"

Errorをキャッチしたが、運営に通知したいときのログルーター

Functions内でconsole.XXXするとSeverityがDEFAULTになってしまうので、以下のように対策した

reportErrorLogという関数を作る

export type LoggerParams = {
  error: Error,
  message: string,
  userId: string,
  logLevel: LogSeverity | 'DEFAULT'
}

export const reportErrorLog = ({ error, userId, logLevel, message }: LoggerParams): void => {
  console.warn(JSON.stringify({
    logLevel,
    userId,
    message: `${message} : ${error.message}`,
  }))
  console.debug(error.stack)
}

JSON.stringifyするのが個人的にポイント

ログルーターの設定

以下のようにする

resource.type="cloud_function"
resource.labels.project_id="XXXX"
severity>="ERROR" OR (severity="DEFAULT" AND jsonPayload.logLevel="ERROR")

severity="DEFAULT" AND jsonPayload.logLevel="ERROR"
が肝。JSON文字列でログを流すとjsonPayloadにログ本文が入るので、そこからlogLevelを抜き取る。これが上記ソースのLoggerParams型と一致しており、ソース上でERRORレベルに設定されたログはSlackに流す、というようにコントロール可能となる

PubSubハンドラ

ログルーターを設定して、PubSubトピックが作れたら、今度はハンドラを実装する

export default () => {
  return functions.pubsub
    .topic('XXX')
    .onPublish(async (pubSubEvent: Message) => {
      const dataString = Buffer.from(pubSubEvent.data, 'base64').toString();
      const data = JSON.parse(dataString) as Data;

      await send(buildBody(data));
    });
};

他のメソッドは省略するがこんな感じ。


以上で、Firebase Cloud FunctionsにおいてthrowされたErrorとcatchしたErrorをそれぞれSlack通知することが実現できた。