GCPのCloud Functionsでは、トラブルシューティングが比較的面倒です。他の環境と異なり実行環境にログインして確認するようなことができないので、やむを得ず想像力だけでデバッグすることもあるように思います。
こうした状況で助けになる武器の一つが、手元で動くユニットテストです。手元でバグを再現できるなら普段の開発と同じ武器が使えますし、再現しないにせよ原因箇所を狭める助けにはなるでしょう。
もちろん、ユニットテストだけで全機能をカバーできるわけではありません。たとえばGCPに特化した機能を使いたくてCloud Functionsを使っている場合など、クラウド環境でしか再現しないバグも考えられます。そうした状況ではユニットテストは無力です。
そこで次に重要になってくるのがログ出力です。Cloud FunctionsのログはCloud Logging1で管理されているので、ログが巨大でも必要な情報を簡単に取り出すことができます。あとは必要なログをどう残すかが課題ということになります。
本稿ではCloud Functionsでこういうログを出すと便利ですよ、という個人的見解を紹介します。コード例はPythonですが、他の言語にも適用できる内容だと思います。
Cloud Functionsのログとログレベル
Cloud Functionsのログは次の3種類のレベルで記録されます。
- ERROR (標準エラー出力)
- INFO (標準出力)
- DEBUG (システムによる出力)
DEBUGレベルではCloud Functionの起動と終了が表示されます。残りの2つのレベルは利用者が自由に使えます。これはランタイム言語が何であっても同じはずです。
エラーを追いやすくするために(1)
Cloud Functionsの場合、エラーが出た場所をすぐ特定できることが極めて重要です。私は次のようなことが大切ではないかと考えています。
- エラーチェックをマメに書く
- エラーが出たら即座に例外を発生させる
- スタックトレースをログに出して、エラー箇所を追いやすくするため
- 例外メッセージに、そのコンテキストで重要な変数値などを入れ込む
if ret == {}:
raise Exception(f'No JSON file in the zip file: {zip_filename}')
これはCloud Functionsに限らない一般論ではありますが、こういう環境だと一段と大事ですね。
エラーを追いやすくするために(2)
既存ライブラリが例外を出す場合、そのままログに表示させてもいいのですが、例外を投げ直した方が良いことも珍しくありません。
try:
return [permission() for permission in self.permission_classes]
except TypeError as e:
raise TypeError(f'独自のエラーメッセージ {重要な変数値などを含める}') from e
このように、ログを見る人が状況を把握しやすくなるようなメッセージを追加して投げ直すのが良いでしょう。
エラーを追いやすくするために(3)
基本的には例外メッセージだけでトラブルが追えるようにしておくべきですが、それ以外にも必要最低限の情報をINFOレベルで出力しておくと便利です。
print(f'Processing "{zip_filename}" ...')
INFOレベルの情報にしておけば、エラーだけ追いたいときはフィルタで消すこともできますから邪魔になりません。
まとめ
- ログは大事だよ
- (知ってた)
参考URL
-
旧名Stackdriver Logging。巨大なログが相手でも高速に全文検索ができちゃう、他所でもよく見る奴。中身はBigTableなんですかね…?[要出典] ↩