[GAS（Google Apps Script）]スプレッドシートで完結！404や500エラーを検知して自動メール通知

Webサイトの管理・保守業務などをやっている方、管理サイトの監視ってどのように実施されていますか？
例えば、何らかの理由で突然ページが表示されなくなったりしたら、いち早く検知して対処する必要があります。
大掛かりなプロモーションや、ふとしたきっかけでSNSでバズったりして、急激なトラフィックの増加により500エラーが発生した…など、割とWeb業界あるあるではないでしょうか。

新しいツールを導入せずに馴染みのあるツールで簡単に運用できる方法はないかと考え、今回はスプレッドシートを使って監視対象URLを管理し、エラーを検知したらメール通知する仕組みを作成してみました。

## 要件

* 対象URLはスプレッドシートで管理
* HTTPステータスエラーのみ監視対象とする（コンソールエラーなどは対象外）
* エラーを検知したらメール通知
* エラーの検知タイミングは自由に編集できるものとする

## 実装

上記要件に適した方法として、GAS（Google Apps Script）を利用して、スプレッドシートのみで簡潔させることにしました。

### 準備

まずは管理用のスプレッドシートを用意します。
シート名を「URL一覧」に変更します。※シート名を指定してスクリプトが動くので任意の名称にする。

シートの中身は以下を例に、A列・B列・C列を設定してください。
監視したいURLはB4以降に記載していきます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62022/a5b99f87-a2ca-2100-7464-7b9ae0267e9f.png" width="500">

* A列はサイト名（任意項目）を、C列はエラー番号（500とか404）が自動記入されるので空白にしておく

### GASソース
メニューの 拡張機能　＞　Apps Script　を選択し、以下のソースを記入する。
※プロジェクト名、スクリプト名は任意

```javascript
function checkStatusAndSendEmail() {
    var emailAddress = 'sample@hogehoge.com'; // メール通知先

    var file = SpreadsheetApp.getActiveSpreadsheet();
    var fileUrl = file.getUrl();

    var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('URL一覧'); // シート名を指定
    var data = sheet.getRange('B4:B').getValues(); // 対象列（B4以降）範囲指定
    var errorArray = []; // 検知したエラーURLを収める配列

    // 実行日時取得
    var actionDate = new Date();
    actionDate = actionDate.toLocaleString('ja-JP');

    // 見出し取得
    var titleA = sheet.getRange('A3').getValues(); // A列
    var titleB = sheet.getRange('B3').getValues(); // B列
    var titleC = sheet.getRange('C3').getValues(); // C列

    // 前回の内容をリセット
    sheet.getRange('C4:C').clear(); // 現ステータスをリセット
    sheet.getRange('D4:D').clear(); // 前回の検出日時をリセット

    // ここから処理
    for (var i = 0; i < data.length; i++) {
      var url = data[i][0];
      if (url) {
        try {
          var response = UrlFetchApp.fetch(url, {muteHttpExceptions: true});
          var status = response.getResponseCode();

          // ステータス400番台以上のエラー
          if (status >= 400) {
            var pageName = sheet.getRange(i + 4, 1).getValues();

            errorArray.push([pageName, url, status]);
            sheet.getRange(i + 4, 3).setValue(status); // スプレッドシートのエラー情報更新
            sheet.getRange(i + 4, 4).setValue(actionDate); // 検出日時更新
          }
        } catch (e) {
          var pageName = sheet.getRange(i + 4, 1).getValues();

          errorArray.push([pageName, url, e]);
          sheet.getRange(i + 4, 3).setValue(e); // スプレッドシートのエラー情報更新
          sheet.getRange(i + 4, 4).setValue(actionDate); // 検出日時更新
        }
      }
    }
    
    if (errorArray.length > 0) {
      var emailBody =
        '<html><body><p>下記のURLでHTTPエラーが発生しています。' + '<br>' + actionDate.toString() + '</p>'
        + '<table border=\"1\" style=\"border-collapse: collapse\"><tr><td style=\"padding:5px; background-color:#efefef; text-align:center;\">' + titleA + '</td><td style=\"padding:5px; background-color:#efefef; text-align:center;\">' + titleB + '</td><td style=\"padding:5px; background-color:#efefef; text-align:center;\">' + titleC + '</td></tr>';
      for (var i = 0; i < errorArray.length; i++) {
        emailBody += '<tr><td style=\"padding:5px;\">' + errorArray[i][0] + '</td><td style=\"padding:5px;\">' + errorArray[i][1] + '</td><td style=\"padding:5px;\">' + errorArray[i][2] + '</td></tr>';
      }
      emailBody += '</table>' + '<br>▼管理URL一覧<br>' +  fileUrl + '<br>' + '</body></html>';

      GmailApp.sendEmail(emailAddress, 'HTTPステータスエラーの検出（' + actionDate.toString() + '）', '', {htmlBody: emailBody});
    }
}
```

* 一行目の```var emailAddress = 'sample@hogehoge.com'; // メール通知先```に、エラーが発生した際の通知先メールアドレスを記入する。
* ```var emailBody = '...'```からが通知メールの本文。内容は適宜アレンジしてください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62022/fe6cd922-7b4d-f8e9-5447-c00b31a3cc55.png" width="600">

メニューの「実行」をクリックします。アクセス権の承認を求めるポップアップが表示されるので承認してください。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62022/2c11b91f-8646-c344-6dd8-6da65a909181.png" width="400">

エラーを検知していれば、ソースで指定したメールアドレス宛に通知メールが届きます。
エラーがなければ特にアクションは起こらないため、一度ダミーのURLなどで404エラーが検知されているかテストすることをおすすめします。

メールサンプル
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62022/9443e2d5-0d64-9fb4-0eba-0097d598c747.png" width="500">

### 定期監視スタート

メールが問題なく届くのを確認したら、監視スケジュールを設定します。
Apps Script画面の左メニュー「トリガー」をクリックし、「イベントのソースを選択」から「時間主導型」を選択します。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/62022/c91c0c16-40b4-3f1b-9552-0f411745b91b.png" width="500">

任意の監視タイミングに合わせ、「1時間おき」「1日おき」などを設定してください。
※エラーが検知されなければメールは飛ばないので、1時間おきと指定したとしても、必ず1時間おきにメールが届くわけではありません。

これで、万が一管理サイトがステータスエラーを起こしていても早期に気づくことができます。
一点注意したいのが、ドメインだけ指定してもサイト全体を監視するわけではないため、あくまでもページ単位で記載する必要があります。

## 余談

ちなみに、上記ソースはいま話題の[ChatGPT](https://chat.openai.com/)に手伝ってもらいました。
まずは求める要件を伝え、スプレッドシートだけで完結するかを質問し、完結するソースを生成してもらいました。
途中なんどか書き直してもらったり最終調整は自分で書きましたが、これまでは要件に合わせてググってサンプルソースを探していたので、自然言語だけでベースのソースが生成されるのは大変便利でした。