
CloudWatch Logs を文字列検知してログ内容をメールを送信してみた
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。 ご機嫌いかがでしょうか。 "No human labor is no human error" が大好きな吉井 亮です。
本記事に記載の手段でのログ通知は以下の理由により推奨されないことが判明しました。
1. logs.filter_log_events でログを取得していますが、厳しいハードリミットがあり変更ができません。
  2. CloudWatch Logs にログが取り込まれた時間、CloudWatch Alarm がカウントされる時間、Lambda 関数を実行する時間の ”差” に対する考慮がシビアであり取りこぼしが否定できません。 
  3. ログの取り込み時刻とログのタイムスタンプの間に乖離がある可能性があり、その場合取りこぼしが否定できません。 
  
本記事を参考に実装された方々へご迷惑をおかけしたことをお詫びいたします。
 サブスクリプションフィルターを使用した通知を検討ください。 
アプリケーションや OS のログファイルから特定の文字列をマッチし メール等で担当者へ通知する運用はクラウドにおいても必要です。
今回は CloudWatch Logs に出力しているログファイルから 特定文字列マッチを行い、SNS で通知する方法を試してみます。
構成
ログの流れは以下の通りです。
- EC2 等から CloudWatch Logs へログファイル出力
- 特定文字列をマッチさせて CloudWatch Alarm で検出
- CloudWatch Alarm から Lambda Function を起動
- Function で CloudWatch Logs を検索、該当文字列が含まれた行を抽出
- 該当文字列が含まれた行を SNS で通知

実装
それでは実装していきます。
アラーム用 SNS トピックの作成
まず CloudWatch Alarm から呼び出すトピックを作成します。 SNS トピック を開きます。 トピックの作成 をクリックします。
”トピックの作成” 画面で任意の名前と表示名を入力し、画面下部の トピックの作成 をクリックします。
メール通知用 SNS トピックの作成
このトピックはメール通知で使います。 SNS トピック を開きます。 トピックの作成 をクリックします。
[トピックの作成] 画面で任意の名前と表示名を入力し、画面下部の トピックの作成 をクリックします。
次の画面で サブスクリプションの作成 をクリックします。 プロトコルを Eメール に、エンドポイントに ご自身のメールアドレス を入力し、画面下部の サブスクリプションの作成 をクリックします。
しばらくすると AWS Notification - Subscription Confirmation という件名のメールが来ると思います。 本文をよく確認して Confirm を行ってください。
Lambda 関数の作成
Lambda 関数 を開きます。
関数の作成 をクリックします。
設計図の作成 を選択します。 sns-message-python を検索して選択してから 設定をクリックします。
次の画面では以下の情報を入力し関数の作成をクリックします。
| 項目 | 値 | 
|---|---|
| 関数名 | 任意 | 
| 実行ロール | 基本的な Lambda アクセス権限で新しいロールを作成 | 
| SNS トピック | 前の手順で作成したトピックを指定 | 
| トリガーの有効化 | チェックを入れる | 
コード
”関数コード” 画面では ランタイム を Python 3.6 にします。
コードは以下を貼り付けます。
from __future__ import print_function
import json
import boto3
from botocore.exceptions import ClientError
import datetime
import os
print('Loading function')
def lambda_handler(event, context):
    #print("Received event: " + json.dumps(event, indent=2))
    message_orig = event['Records'][0]['Sns']['Message']
    message = json.loads(message_orig)
    
    SNS_TOPIC_ARN = os.environ['SNS_TOPIC_ARN']
    try:
        logs = boto3.client('logs')
        sns = boto3.client('sns')
        
        # SNS message からメトリックネームとネームスペースを取得
        metricfilters = logs.describe_metric_filters(
            metricName = message['Trigger']['MetricName'] ,
            metricNamespace = message['Trigger']['Namespace']
        )
        
        # CloudWatch Alarm のピリオドの2倍 + 10秒
        intPeriod = message['Trigger']['Period'] * 2 + 10
        
        # CloudWatch Logsを検索するために必要な項目のセット
        # unixtime へ変換している
        strEndTime = datetime.datetime.strptime(message['StateChangeTime'], '%Y-%m-%dT%H:%M:%S.%f%z')
        strStartTime = strEndTime - datetime.timedelta(seconds = intPeriod)
        alarmEndTime = int(strEndTime.timestamp()) * 1000
        alarmStartTime = int(strStartTime.timestamp()) * 1000
        
        # CloudWatch Logsを検索して該当のログメッセージを取得する
        response = logs.filter_log_events(
            logGroupName = metricfilters['metricFilters'][0]['logGroupName'],
            startTime = alarmStartTime,
            endTime = alarmEndTime,
            filterPattern = metricfilters['metricFilters'][0]['filterPattern'],
            limit = 10
        )
    
        for i in range(len(response['events'])):
            #SNSのタイトル、本文整形
            alarm_name = message['AlarmName']
            new_state = message['NewStateValue']
            reason = response['events'][i]['message']
    
            #SNS Publish
            publishResponse = sns.publish(
                TopicArn = SNS_TOPIC_ARN,
                Message = "state is now %s: %s" % (new_state, reason),
                Subject = alarm_name
            )
        
    except Exception as e:
        print(e)
環境変数
環境変数に SNS_TOPIC_ARN を入力し、値には前の手順で作成したメール通知用トピックを入力します。

タイムアウト
関数のタイムアウトを1分に設定します。 ※仮に1分にしていますが実際の環境に合わせてチューニングしてください。

実行ロールにポリシーをアタッチ
実行ロールという項目に IAM コンソールへのリンクが表示されています。 そちらをクリックします。

IAM ロールが表示されたら ポリシーをアタッチします をクリックします。
CloudWatchLogsReadOnlyAccess と AmazonSNSFullAccess をアタッチします。 ※手順を簡易化するために大きめの権限を付与しています。 ※実際の運用では適切な権限設定をお願いします。
メトリクスフィルタの作成
マネジメントコンソールで CloudWatch Logs を開きます。
対象のロググループにチェックを入れ メトリクスフィルタの作成 をクリックします。
フィルタパターン にマッチさせたい文字列を入力します。 構文の詳細は フィルターとパターンの構文 を参照ください。
次の画面では以下の通り入力し フィルタの作成 をクリックします。
| 項目 | 値 | 
|---|---|
| メトリクス名 | 任意 | 
| メトリクス値 | 1 | 
| デフォルト値 | 0 | 

アラームの作成
メトリクスが出来ました。 作成したフィルタの アラームの作成 をクリックします。

”メトリクスと条件の指定” 画面では以下を編集しました。 要件に合わせて変えてください。
| 項目 | 値 | 説明 | 
|---|---|---|
| 期間 | 1分 | メトリクスを評価する期間 | 
| アラーム条件を定義 | 以上 | しきい値の条件 | 
| よりも | 1 | しきい値を定義 | 
| 欠落データの処理 | 欠落データを適正 | データが取得出来ないときは正常と判断 | 


”アクションの設定” 画面では 既存の SNS トピックを選択 をチェックし 最初の手順で作成したアラーム用トピックを指定します。
”名前と説明” 画面で任意の名前を付けてアラームを作成します。
テスト
OS 上のログファイルにテストメッセージを出力してみます。
$ echo "ERROR: this is test" >> test_log
メール
しばらくするとメールが届きました。 成功しました。

まとめ
商用のツールや Amazon ES を使うときめ細やかな設定が出来そうですが なるべく費用を抑える形であれば上記が使えそうです。
AWS を使うから何が何でも CloudWatch というわけではなく 要件、コスト、メンテナンス性などを考えて色々な選択肢を考えていきたいものです。
以上、吉井 亮 がお届けしました。












