GASのエラー通知をLINE Botで行い開発効率爆上げする


この記事は実際にGASの開発で得られた知見によるものです。

効果が大きく期待できる想定環境

・GASとWebhook経由でSlack APIなどの外部通信を行う
・スプレッドシートなどのGoogle Appsを連携するシステムの開発及び運用
・エラーが発生していることを迅速に知る必要があるGAS案件

問題提起

Webhook経由でトリガーされる関数では,以下のようにログを出力しても
Stackdriver Loggingではこのログや、エラー発生時の詳細な実行状況を把握できない仕様になっています。

(webhook経由でトリガーされる関数内にて)
Logger.log('Webhook triggered.')

このため、この状況下でエラーの検出や、原因の特定が難しいです。
また上記の状況でなくとも、Stackdriver Loggingでは遅延が発生するので
エラー発生の瞬間にスムーズに状況を把握するのは難しいです。

解決策 サマリー

LINE Botを作成し、それにエラー通知を行わせる。
エラー通知グループを作成し、LINE Bot, 開発者, クライアントを追加する。
グループIDを取得し、そのグループにエラー発生時にエラーメッセージを送信する。

それには以下を含める:

・エラー内容
・エラー発生時間
・エラーのコード内の発生箇所
・エラーの発生したスプレッドシートの名前
・エラーの発生したスプレッドシートのURL

もたらされた効果

実際の運用の様子:

・他の開発者が開発を行っている時にエラーの発生状況を瞬時に共有できるようになった。
 →原因についてディスカッションできるようになった
・エラーの発生状況をクライアントも瞬時に知ることができるようになった。
 →これにより、万が一の時は迅速に代替手段に切り替えるなどの対応ができうるようになった。
・エラーの発生箇所やファイルが明確になり、すぐにそのファイルにリンクから飛べるようになった。

要約すると、開発効率を爆上げすることができた。

解決策 - 具体的な実装方法

LINE Botの作成やGroup IDの取得については割愛します。
この部分の詳細は、例えば私の書いた以下の記事を参照してください:
https://qiita.com/kanta_yamaoka/items/dd830cf23ff59353824d

1.LINE_ErrorReport(error)を定義

LINE_ErrorReport(error)の定義
/**
*発生したエラーを特定のLINEグループに通知します。
* カスタムなエラーを投げる場合は以下をtry{}のブロック内で記述してください。
* throw new Error('これはテストです。これはテストです。')
*@param {Error} error - try&catch(e)により得られるエラーeのこと
*@return {undefined}
*/
function LINE_ErrorReport(error) {
  const date = new Date().toLocaleString({ timeZone: 'Asia/Tokyo' })
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
  const fileName = sheet.getName();
  const fileUrl = sheet.getUrl();
  let errorDetails = `\n${error.stack}`
  let messageTemplate = `${date}\n「${fileName}」のスクリプトにてエラー発生を検出しました😔: ${errorDetails} \n\nより詳しい情報は以下のURLよりStackdriver Logging上でご確認ください。\nシートのURL:\n ${fileUrl}`

  // LINE Messaging API config.
  var url = 'https://api.line.me/v2/bot/message/push';
  const ACCESS_TOKEN = '{EDIT HERE}'
  const groupID = '{EDIT HERE}'
  UrlFetchApp.fetch(url, {
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8',
      'Authorization': 'Bearer ' + ACCESS_TOKEN,
    },
    'method': 'POST',
    'payload': JSON.stringify({
      'to': groupID,
      'messages': [{
        'type': 'text',
        'text': messageTemplate,
      }]
    })
  })
}

2.try & catchでエラーの発生しうる箇所をラップ

WebhookでのSlack APIなど外部へ通信しうる部分や、それを利用する部分をtry&catchでラップしてください。

以下の例を考えましょう。大概の場合うまく行きますが、まれに、

fetchedUserIDsが何らかの原因でnullやundefinedになった際

にforEachメソッドは呼べないので、エラーが発生します。

エラーの発生しうる箇所の例

const fetchedUserIDs=...API経由でデータを取得...;
fetchedUserIDs.forEach(user=>{...何らかの処理...})

これに対しては以下のようにtry&catchでラップしてください:

try&catchでのラップ例

try{
  const fetchedUserIDs=...API経由でデータを取得...;
  fetchedUserIDs.forEach(user=>{...何らかの処理...});
}catch(e){
  LINE_ErrorReport(e);
}

3. Optional - カスタムエラーを投げよう

余談ですが、エラーが具体的に想定される場合は、以下のようにカスタムエラーを投げると
エラーが起きた時に対処の方法が、自分以外の開発者にもわかりやすいです。
表面的なエラー情報より、それをもたらしている本質的な情報を得た方が、デバッグがしやすいですよね?

表面的なエラー情報:fetchした情報を配列がundefinedになっている。
本質的なエラー情報:情報をfetchする段階でエラーが起きた。

余談,カスタムエラーと便利な分岐のショートハンド
try{
  const fetchedUserIDs=...API経由でデータを取得...;
  if(!fetchedUserIDs){
    throw new Error('fetchedUserIDsが正常に読み込めませんでした。')
  }
  fetchedUserIDs?.forEach(user=>{...何らかの処理...});
}catch(e){
  LINE_ErrorReport(e);
}

なお、上記で見られる 変数名? という表記は、

ある変数が存在する場合に、そのメソッドを呼び出すという分岐のショートハンド

で、わざわざif文を書かなくとも簡潔な表現が可能です。

まとめ

Webhookで処理を行う場合は、詳細なエラーがStackdriver Loggingで取得できない。
try&catchで取得したエラー詳細をLINE Botで通知することにより、その問題は解決される。
また、それだけでなく関連する部分の全体的な開発効率が爆上がりし、エラー発生時の対処も冷静に行える。

余談 - Slack APIやdiscordで通知もオススメ

LINEはエラー通知手段としては以下の利点があります。

・エンジニアでない人でも扱いやすい
・アクティブユーザーが多く確実な通知が期待できる

ですが、プライベートでも使うLINEに仕事のストレスを侵入させるのが嫌な人もいるでしょう。

ですので、例えば、Slackが好みの人はSlack Incoming Webhookをエラー通知に利用してもいいでしょう。

あなたもGASで幸せなエンジニアライフを!それではご機嫌よう!