CloudWatchのXML形式アラートログをLambdaを介しSNSで通知する方法

この記事を書いたメンバー:

榊原慶太

CloudWatchのXML形式アラートログをLambdaを介しSNSで通知する方法

目次

ハイブリッドクラウドコンサルティング部の榊原です。今回はEC2(Windows)からCloudWatchに送られたログをLambdaを介して、SNSで送る方法について述べます。

前提

  • OSはWindows 2022
  • EC2にCloudWatch Agentはインストール済
  • ロググループへのログ出力が正常に行えている
  • SNSトピックは既に作成済み

実装内容

CloudWatch AgentがインストールされたEC2(Windows)からCloudWatchロググループにログが送られています。サブスクリプションフィルターで必要なログをLambdaに渡し、そこからさらに必要な値のみ抜き出します。そして最後にSNSからユーザに向けてメールで送信するという内容です。

手順

ログの出力内容の確認

記事作成時点で出力されているログは以下の通りです。

 <Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'><System><Provider Name='System'/><EventID Qualifiers='0'>101</EventID><Version>0</Version><Level>2</Level><Task>0</Task><Opcode>0</Opcode><Keywords>0x80000000000000</Keywords><TimeCreated SystemTime='2023-08-03T07:22:02.6362567Z'/><EventRecordID>66948</EventRecordID><Correlation/><Execution ProcessID='0' ThreadID='0'/><Channel>System</Channel><Computer>XXXXX</Computer><Security UserID='XXXXX'/></System><EventData><Data>テストです</Data></EventData><RenderingInfo Culture='en-US'><Message></Message><Level>Error</Level><Task></Task><Opcode>Info</Opcode><Channel></Channel><Provider></Provider><Keywords><Keyword>Classic</Keyword></Keywords></RenderingInfo></Event>

Errorとあるため、これがついたログのみサブスクリプションフィルターに通しましょう。

Lambda作成

ランタイムはPython3.9、タイムアウトは10秒としました。Lambdaにアタッチしているロールには「AWSLambdaBasicExecutionRole」と「AmazonSNSFullAccess」をアタッチしています。下記コードを入力します。SNSトピックのARNはご自身のものに書き換えてください。ちなみに僕は知らなかったのですが、サブスクリプションフィルターを介して宛先サービスに送信されるログは、base64 でエンコードされ、gzip 形式で圧縮されるのでご注意ください。(AWS公式URLより)

import boto3
import json
import base64
import gzip
import re

def lambda_handler(event, context):
    # CloudWatchのログデータはBase64エンコードが施されているのでデコード
    log_data_base64 = event['awslogs']['data']
    log_data_bytes = base64.b64decode(log_data_base64)

    # gzip圧縮されたログデータを解凍し、文字列にデコード
    log_data_str = gzip.decompress(log_data_bytes).decode('utf-8')

    # JSON文字列を辞書に変換
    log_data_dict = json.loads(log_data_str)

    # owner(アカウントID)とlogStream(インスタンスID)の抽出
    accountID = log_data_dict['owner']
    instanceID = log_data_dict['logStream']

    # 正規表現を使ってErrorメッセージを抽出
    error_messages = re.findall(r"<Message>(.*?)<\/Message>", log_data_str)

    if not error_messages:
        print("No Error found in log events.")
        return {
            'statusCode': 200,
            'body': 'No Error found in log events.'
        }

    # SNSクライアントを初期化
    sns_client = boto3.client('sns')

    # メール送信のためのSNSトピックARNを指定
    sns_topic_arn = 'arn:aws:sns:XXXXX'

    # メッセージを文字列に変換
    error_message_str = "\n".join(error_messages)
    message_str = json.dumps({"Log Message": error_message_str})

    # メッセージをまとめる
    message = "messagesログにアラート文字列が出力されました。\nログ内容を確認しご対応ください。"
    log_message = message_str
    email_message= f"{message}\n\nアカウントID:{accountID}\n\nインスタンスID: {instanceID}\n\n{log_message}"

# メール送信
    try:
        subject = "Test Data Extracted from CloudWatch Logs"
        response = sns_client.publish(
            TopicArn=sns_topic_arn,
            Message=email_message,
            Subject=subject
        )
    except Exception as e:
        print(e)

Lambdaのeventに渡されたデータはデコードを施し、JSONにすると下記のようになります。

 {
    "messageType": "DATA_MESSAGE",
    "owner": "XXXXX",
    "logGroup": "System",
    "logStream": "i-XXXXX",
    "subscriptionFilters": [
        "test_filter_onlyError"
    ],
    "logEvents": [
        {
            "id": "XXXXX",
            "timestamp": 1691043334468,
            "message": "<Event xmlns='http://schemas.microsoft.com/win/2004/08/events/event'><System><Provider Name='System'/><EventID Qualifiers='0'>101</EventID><Version>0</Version><Level>2</Level><Task>0</Task><Opcode>0</Opcode><Keywords>0x80000000000000</Keywords><TimeCreated SystemTime='2023-08-03T06:15:34.4680516Z'/><EventRecordID>66907</EventRecordID><Correlation/><Execution ProcessID='0' ThreadID='0'/><Channel>System</Channel><Computer>XXXXX</Computer><Security UserID='XXXXX'/></System><EventData><Data>テストです</Data></EventData><RenderingInfo Culture='en-US'><Message></Message><Level>Error</Level><Task></Task><Opcode>Info</Opcode><Channel></Channel><Provider></Provider><Keywords><Keyword>Classic</Keyword></Keywords></RenderingInfo></Event>"
        }
    ]
}

サブスクリプションフィルターの準備

対象のロググループから「Lambdaサブスクリプションフィルターを作成」を選択します。

Lambda関数は上で作成したものを選択し、ログの形式は「その他」、サブスクリプションフィルターのパターンは抜き出したい文字列を入力するので今回は「Error」とします。サブスクリプションフィルター名は自由です。テストするログデータは、対象のインスタンスIDを選択します。全て入力したら、「ストリーミングを開始」を選択します。

確認

準備が整ったので、実際にErrorを検出して通知が来るか試してみましょう。
インスタンスにRDP接続し、下記コマンドを入力します。
仮のログデータが作成されます。SUCCESSと表示されればOKです。

C:\Users\Administrator>eventcreate /l SYSTEM /t ERROR /id 123 /d "テストです"
SUCCESS: An event of type 'ERROR' was created with 'SYSTEM' as the log.

まもなく僕のアドレスに以下のようなメールが届きました。

正しく動作していそうです。今回Log Messageには何も入れていないので空白のままですが、実際には例えば下記の赤マーク部分のメッセージが出てきます。(文字が小さいので拡大してご確認ください汗)

最後に

今回はXML形式のログデータをCloudWatchサブスクリプションフィルターとLambda、SNSを使って通知する仕組みをご紹介しました。この記事がどなたかのお役に立てば幸いです。ここまでお読みいただきありがとうございました。

カテゴリー
タグ

この記事を書いたメンバー

榊原慶太
榊原慶太

技術検証、re:Invent参加、AWS資格全冠のための勉強教材等、幅広く記事にしています。皆様のお役に立てば幸いです。最近は会社ブログメインで記事投稿しています。

SAPシステムや基幹システムのクラウド移行・構築・保守、
DXに関して
お気軽にご相談ください

03-6260-6240 (受付時間 平日9:30〜18:00)