CloudWatch Logsの特定文字を検知してログ内容を通知するLambda Function

CloudWatch Logsにサブスクリプションフィルター設定して、特定文字を含むログデータをLambda FunctionつかってChatworkに通知しました。
2021.01.20

CloudWatch Logsへの特定文字の書き込みを検知する仕組みとして、メトリクスフィルターを利用しCloudWatch Alarmを設定する構成は多くあります。こちらの構成は、CloudWatch Alarmの状態変化を示す通知となり、検知したログデータそのものを通知に含めることはできません。

▲ メトリクスフィルターを利用したCloudWatch Alarmの通知例

サードパーティのログインテグレーション等を利用せず、CloudWatch Logsのログデータを通知に含めたいといった場合は少々作り込みが必要です。今回はCloudWatch Logsにサブスクリプションフィルターを設定し、検知したログデータを通知するLambda Funcionを作成してみました。

前提

通知先はChatworkとし、通知に利用するSNSトピック、Lambda Functionは以下を利用します。

構成

前提に記載のLambda Function等を利用するため、以下赤枠内が今回のスコープです。CloudWatch Logsのサブスクリプションフィルター設定と、SNSトピックにログデータを送信するLambda Functionの作成を本ブログで行います。

構成図

構築

Lambda Function作成

ランタイムはPython 3.8で、以下Lambda Functionの設定です。

関数コード

cwl-to-sns-publish

import logging
import json
import base64
import gzip
import boto3
import os

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    sns_topic_arn = os.environ['SNS_TOPIC_ARN']  # 環境変数よりSNSトピックARN取得
    sns_client = boto3.client('sns')

    # CloudWatchLogsからのデータはbase64エンコードされているのでデコード
    decoded_data = base64.b64decode(event['awslogs']['data'])
    # バイナリに圧縮されているため展開
    json_data = json.loads(gzip.decompress(decoded_data))

    logger.info("EVENT: " + json.dumps(json_data))

    # ログデータ取得
    message = json_data['logEvents'][0]['message']

    # SNS件名設定
    log_group = json_data['logGroup']
    log_stream = json_data['logStream']
    #ロググループ名、ログストリーム名をsubjectに設定
    subject = log_group + ' ' + log_stream

    response = sns_client.publish(
        TopicArn = sns_topic_arn,
        Subject = subject,
        Message = json.dumps(message)
    )

環境変数

sa20210118-04

本Lambda Functionはサブスクリプションフィルター設定により、Lambda Functionに送信されたCloudWatch LogsのログデータをSNSトピックに送信します。利用するSNSトピックのARNを環境変数に指定しています。

CloudWatch Logsのサブスクリプションフィルター設定により、実行されるLambda Functionは{ "awslogs": {"data":"BASE64ENCODED_GZIP_COMPRESSED_DATA"} }イベントデータを受け取ります。dataキーはBase64 でエンコードされており、gzip 形式で圧縮されています。ログデータを取得するため、デコード、展開を実施します。

その後、任意の件名(ここでは、ロググループ名 + ログストリーム名)を指定し、ログデータをSNSトピックに送信しています。

なお、後続のLambda Function(前提に記載のChatworkにメッセージを送信するLambda Function)でjson.loadを行っており、そちらのLambda Functionに修正を加えないとタブ等の制御文字が扱えないため、publishメソッド呼び出しの際にjson.dumpsでエスケープしてログデータを送信しています。通知内容等この辺りをカスタマイズする際は、後続処理も考慮した修正が必要です。(今回は後続のLambda Functionの修正は行っていません。)

CloudWatch Logsサブスクリプションフィルター設定

該当のロググループに、Lambdaサブスクリプションフィルターを設定します。任意のフィルター名で先程作成したLambda Functionを送信先に指定します。

ここでは、フィルタパターンを"ERROR"とし、特定の書き込み(ここではERROR)を含むログデータのみLambda Functionに送信するようにしています。フィルターの詳細については、以下を確認ください。

▲ Lambdaサブスクリプションフィルターの設定

CloudWatch Logsにてサブスクリプションフィルターの設定を行うと、該当のLambda Functionにトリガー等が付与されます。

通知確認

Lambdaサブスクリプションフィルターを設定したロググループに、フィルタパターンに合致する書き込みを行いChatworkへの通知を確認します。put-log-eventsでログの書き込みが可能です。

▲ ERRORを含むロギングを実施

Chatwork通知に、ログデータが含まれていることを確認できました。

sa20210118-09

さいごに

EventBridgeのイベントデータにログデータがあれば、イベントパターンの定義のみで作り込み不要でログデータを取得できるかな?とも考えましたが、CloudWatch Logsは、CloudTrailを介してイベントとなり、ログデータは含まれていませんでした。

APIコールのイベントとなり、以下のようなイベントデータでした。

CloudTrailを介したCloudWatch Logsのイベント例
{
   "version":"0",
   "id":"56ae36c7-dfb1-6966-2778-52434364d2a5",
   "detail-type":"AWS API Call via CloudTrail",
   "source":"aws.logs",
   "account":"XXXXXXXXXXXX",
   "time":"2021-01-20T00:21:10Z",
   "region":"ap-northeast-1",
   "resources":[
      
   ],
   "detail":{
      "eventVersion":"1.08",
      "userIdentity":{
         "type":"AssumedRole",
         "principalId":"AA:test-error-write",
         "arn":"arn:aws:sts::XXXXXXXXXXXX:assumed-role/LambdaRole/test-error-write",
         "accountId":"XXXXXXXXXXXX",
         "accessKeyId":"",
         "sessionContext":{
            "sessionIssuer":{
               "type":"Role",
               "principalId":"AA",
               "arn":"arn:aws:iam::XXXXXXXXXXXX:role/LambdaRole",
               "accountId":"XXXXXXXXXXXX",
               "userName":"LambdaRole"
            },
            "webIdFederationData":{
               
            },
            "attributes":{
               "mfaAuthenticated":"false",
               "creationDate":"2021-01-20T00:21:01Z"
            }
         }
      },
      "eventTime":"2021-01-20T00:21:10Z",
      "eventSource":"logs.amazonaws.com",
      "eventName":"CreateLogStream",
      "awsRegion":"ap-northeast-1",
      "sourceIPAddress":"52.195.16.227",
      "userAgent":"awslambda-worker/1.0 rusoto/0.42.0 rust/1.47.0 linux",
      "requestParameters":{
         "logGroupName":"/aws/lambda/test-error-write",
         "logStreamName":"2021/01/20/[$LATEST]e0efc2e860504dd1b7ea0f8a8b2c1539"
      },
      "responseElements":null,
      "requestID":"f603bdb5-8647-47c3-8cf9-b2d318cf947b",
      "eventID":"121a21e5-69e4-45bc-94c8-31a2aaeea231",
      "readOnly":false,
      "eventType":"AwsApiCall",
      "apiVersion":"20140328",
      "managementEvent":true,
      "eventCategory":"Management"
   }
}

作り込み不要でログデータの通知を行えるようなアップデートに期待したいと思います。