4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Google スプレッドシートを使ってサーバレスなサイト巡回botを作り、slackに通知させる

Posted at

はじめに

サーバを監視するサービスはサーバ屋のオプションサービスであったりしますが、サーバがダウンしていなくてもDBのテーブルがクラッシュしているとWebサイトが見られない状態になっていることがあり、サーバ監視では気づけないことがあります。

Webサイトを死活監視するプログラムを作っても、それを設置しているサーバがダウンしてしまっては監視になりません。でも、数える程度のWebサイトを監視するためにわざわざ冗長化されたサーバを構築しては費用対効果が合いません。

そういうときに使えるのが Google Apps Script(GAS)です。Googleアカウントを持っていれば無料で使え、サーバも必要ありません。Googleの膨大なインフラで支えられているので滅多なことでは落ちません。slackに通知させるようにすればお客様より先に気づくことができ、バタバタ慌てることもなくなります。

Slack App の作成

まずは Slack App を作って監視通知の準備をします。

  1. Slack App を生成する

    https://api.slack.com/apps にアクセスし、[Create New App] ボタンをクリックします。

    image.png

    アプリの名前やワークスペースを設定します。

    App Name : アプリの名前(日本語可)
    Development Slack Workspace : botを作りたいワークスペースを選択

    image.png

  2. Slack への投稿を有効にする

    Incoming Webhooks に移動し Activate Incoming Webhooks をOnにします。Incoming Webhooks はslackのチャンネルに投稿する機能です。

    image.png

  3. slackでの表示名を設定する

    App Home に戻り、App Display Name の [Edit] をクリック。アプリのslackでの表示名を設定します。

    Display Name (Bot Name) : 表示名(日本語可)
    Default Name : 半角のみ

    Always Show My Bot as Online をOnにします。

    image.png

  4. Slack App が投稿するチャンネルを選択し、Webhook URL を発行する

    Incoming Webhooks に戻り [Add New Webhook to Workspace] をクリック。

    image.png

    投稿したいチャンネルを選択して [許可する] をクリック。

    image.png

    Webhook URL が発行されるので [copy] をクリックしてコピーし、どこかに保存しておきます。

    image.png

  5. Slack で表示されるアイコンを設定する

    Basic Information に移動し、下の方にある Display Information へスクロールしてそれぞれ入力します。

    Short description : アプリの概要
    App icon : slack で表示されるアイコン

    image.png

Google Apps Script の作成

とりあえず slack へ投稿させてみましょう。

  1. Google Apps Scriptのドキュメントを新規作成

    Google Driveにアクセスし、左上の [+新規] から その他 > Google Apps Script を選択。
    ない場合は その他 > アプリを追加 でGoogle Apps Scriptを探して「接続」します。

    image.png

    image.png

    左上にファイルの名前を入力して一旦保存します。

    image.png

  2. スクリプトを書いて投稿できるかテストしてみる

    以下のサンプルコードを入力します。

function myFunction()
{
    const webHook = 'コピーしたURL';
    let send =
    {
        method : "POST",
        payload : JSON.stringify
        ({
            text : "Hello, World !"
        })
    };
    UrlFetchApp.fetch(webHook, send);
}

上のメニューから [実行] をクリックし、slack の指定したチャンネルにメッセージが投稿されているか確認します。

image.png

サイト巡回スクリプトを作成

サイトをチェックして投稿するスクリプトを作成します。

// Webhook URL
const webHook = 'コピーしたURL';

// サイトチェック
// 戻り値はオブジェクトを返すようにして、戻り値で条件分岐だけでなく表示内容の切り替えもできるようにする。
function checkWebsite( site_name, url, keyword, match_is_normal )
{
    let response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });    // muteHttpExceptions オプションを付けてエラーコードを取得できるようにする。
    let response_code = response.getResponseCode();
    // NG
    if( response_code != 200 )
    {
        return { status : "error", error_type : "レスポンスコードエラー", response_code : response_code };
    }
    // html検証
    let content = response.getContentText("UTF-8");
    let match = content.indexOf(keyword) !== -1;
    if(( !match && match_is_normal ) || ( match && !match_is_normal ))
    {
        return { status : "error", error_type : "表示エラー", response_code : response_code };
    }
    return { status : "success", response_code : response_code };
}
// slack 投稿
function postSlack( message )
{
    let send =
    {
        "method": "POST",
        "payload": JSON.stringify
        ({
            "text": message
        })
    };
    UrlFetchApp.fetch(webHook, send);
}
// 巡回
function patrol()
{
    let site_name = "サイト名";
    let url = "サイトURL";
    let keyword = "サイトのHTMLで検索するキーワード";
    let match_is_normal = false;    // keyword がマッチで正常なら true, マッチで異常なら false
    let result = checkWebsite( site_name, url, keyword, match_is_normal );
    // エラーの場合はslack投稿
    if( result.status != "success" )
    {
        let message = "<!channel> サイト異常検知\n" + site_name + "\n" + url + "\n" + result.error_type + "\n" + "レスポンスコード : " + result.response_code + "\n";
        postSlack( message );
    }
}

定期的に実行させる

上のメニューにある [現在のプロジェクトのトリガー] をクリックします。

image.png

右下の [トリガーを追加] ボタンをクリックするとフォームが現れるので、以下のように設定します。ここでは1時間おきに設定しています。

実行する関数 : patrol
実行するデプロイ : Head
イベントのソース : 時間主導型
時間ベースのトリガーのタイプ : 時間ベースのタイマー
時間の間隔 : 1時間おき

image.png

1時間おきに実行されるか確認します。実行されているか分かるように keyword または match_is_normal の値をわざとエラーが出るように設定しておくと良いです。

スプレッドシートのデータを元に巡回させる

このままではチェックできるのが1サイトだけなので、複数サイトをチェックできるように改修します。配列などで設定しても良いですが、せっかく Google Apps Script なので google のスプレッドシートで巡回させるサイトを管理します。スプレッドシートなので非エンジニアでもメンテナンスが可能になります。

また、サイトの文字コードを UTF-8 と決め打ちしていましたが、サイトによって文字コードはまちまち(まあ、殆どはUTF-8ですが)なので、文字コードを指定できるようにします。

スプレッドシートを作成する

巡回するサイトのスプレッドシートを作成し、次のようにセルを用意します。

A列 : サイト名
B列 : サイトURL
C列 : 文字コード
D列 : 表示判定キーワード
E列 : 判定基準(表示判定キーワードがマッチで正常なら true, マッチで異常なら false)

用意できたら、巡回するサイトの情報を入力していきます。
このとき、ちゃんと動作しているかを確認するためにわざとエラーになるサイト情報を1つくらい入れておくと良いでしょう。

image.png

巡回するサイトをスプレッドシートから取得するように改修する

patrol関数を次のように修正します。

// 巡回
function patrol()
{
    const spreadsheet_obj = SpreadsheetApp.openByUrl( spreadsheet_url );
    const sheet = spreadsheet_obj.getSheets()[0];   // 一番左のシート
    const last_row = sheet.getLastRow(); // 最後の行
    // スプレッドシートの2行目から順に巡回
    for(let i = 2; i <= last_row; i++)
    {
        let site_name = sheet.getRange(i, 1).getValue();
        let url = sheet.getRange(i, 2).getValue();
        let charset = sheet.getRange(i, 3).getValue() || "UTF-8";   // デフォルトはUTF-8
        let keyword = sheet.getRange(i, 4).getValue();
        let match_is_normal = sheet.getRange(i, 5).getValue();
        // チェック
        let result = checkWebsite( site_name, url, charset, keyword, match_is_normal );
        // エラーの場合はslack投稿
        if( result.status != "success" )
        {
            let message = "<!channel> サイト異常検知\n" + site_name + "\n" + url + "\n" + result.error_type + "\n" + "レスポンスコード : " + result.response_code + "\n";
            postSlack( message );
        }
    }
}

checkWebsite関数を修正します。引数に文字コードを追加します。

// Webhook URL
const webHook = 'コピーしたURL';
// スプレッドシートURL
const spreadsheet_url = 'スプレッドシートURL';

// サイトチェック
function checkWebsite( site_name, url, charset, keyword, match_is_normal )
{
    let response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });    // muteHttpExceptions オプションを付けてエラーコードを取得できるようにする。
    let response_code = response.getResponseCode();
    // NG
    if( response_code != 200 )
    {
        return { status : "error", error_type : "レスポンスコードエラー", response_code : response_code };
    }
    // html検証
    let content = response.getContentText( charset );
    let match = content.indexOf(keyword) !== -1;
    if(( !match && match_is_normal ) || ( match && !match_is_normal ))
    {
        return { status : "error", error_type : "表示エラー", response_code : response_code };
    }
    return { status : "success", response_code : response_code };
}

エラーが発生したときだけ通知させる

このままだとエラーが解消されないと1時間おきに同じエラー通知が投稿されて無駄な投稿が増えます。前回の巡回結果を記録させて、前回と同じなら通知しないようにします。

巡回プログラムを次のように修正します。

// 巡回
function patrol()
{
    const spreadsheet_obj = SpreadsheetApp.openByUrl( spreadsheet_url );
    const sheet = spreadsheet_obj.getSheets()[0];   // 一番左のシート
    const last_row = sheet.getLastRow(); // 最後の行
    // スプレッドシートの2行目から順に巡回
    for(let i = 2; i <= last_row; i++)
    {
        let site_name = sheet.getRange(i, 1).getValue();
        let url = sheet.getRange(i, 2).getValue();
        let charset = sheet.getRange(i, 3).getValue() || "UTF-8";
        let keyword = sheet.getRange(i, 4).getValue();
        let match_is_normal = sheet.getRange(i, 5).getValue();
        let prev_status = sheet.getRange(i, 6).getValue();
        // チェック
        let result = checkWebsite( site_name, url, charset, keyword, match_is_normal );
        // エラーの場合はslack投稿
        if( result.status != "success" && prev_status != result.status )
        {
            let message = "<!channel> サイト異常検知\n" + site_name + "\n" + url + "\n" + result.error_type + "\n" + "レスポンスコード : " + result.response_code + "\n";
            postSlack( message );
        }
        // 結果を記録
        if( prev_status != result.status ) sheet.getRange(i, 6).setValue( result.status );
    }
}

スプレッドシートに結果が記録されていること、2回目のエラーでは通知されないことを確認してください。

image.png

さいごに

Google Apps Script は javascript をベースにしているので javascript を使える方なら気軽にサーバレスアプリを作れるのでおすすめです。

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?