以下の内容はhttps://dshimizu.hatenablog.com/entry/2025/03/31/002652より取得しました。


CloudWatch Logs へ特定の文字列が出力された場合に Slack に通知させるための AWS SAM アプリケーションのサンプル

CloudWatch Logs へ特定の文字列が出力された場合に, CloudWatch Logs のサブスクリプションフィルターで検知して、それを起点に Slack に通知させる Lambda を実行して通知するための AWS SAM アプリケーションのサンプルです。

Slack Apps の作成と設定

まず、Slack Apps でアプリケーションを作成します。

Scratch で作っても良いです。YAML だと以下のような感じになると思います。

display_information:
  name: CloudWatchLogsNotifyApp   # なんでも良い
features:
  bot_user:
    display_name: CloudWatchLogsNotifyApp   # なんでも良い
    always_online: false
oauth_config:
  scopes:
    bot:
      - calls:read
      - calls:write
      - chat:write
settings:
  org_deploy_enabled: false
  socket_mode_enabled: false
  token_rotation_enabled: false

以下を許可しておきます。

SSM Parameter Store への登録

上記で作成したSlack Botトークン、通知先SlackチャンネルのIDをパラメーターストアに登録します。

SAM アプリケーションの作成

Lambda 関数のコードの作成

src/app.py を作成し、以下のようなものにします。

import json
import os
import urllib.request
import boto3

ssm = boto3.client('ssm')

def get_parameter(name):
    response = ssm.get_parameter(Name=name, WithDecryption=True)
    return response['Parameter']['Value']

def notify_slack(event, context):
    # SSM Parameter Store から Slack OAuth Token と Channel ID を取得
    slack_token = get_parameter(os.environ['SLACK_BOT_TOKEN_PARAMETER'])
    channel_id = get_parameter(os.environ['SLACK_CHANNEL_ID_PARAMETER'])
    
    # CloudWatch Logs サブスクリプションフィルターイベントから送られたデータを処理する
    try:
        # Base64 エンコードされているデータをデコードし、gzip 圧縮を解凍
        compressed_data = base64.b64decode(event['awslogs']['data'])
        decompressed_data = gzip.decompress(compressed_data)
        log_event = json.loads(decompressed_data)

        log_group = log_event['logGroup']
        log_stream = log_event['logStream']
        
        # 各ログイベントを処理
        error_log_message = ""
        for log in log_event['logEvents']:
            timestamp = datetime.fromtimestamp(log['timestamp'] / 1000, timezone(timedelta(hours=+9), 'JST'))
            error_log_message += str(timestamp) + ': '  + "\n" + log['message'] + "\n---\n"
                
        # Slack に送信するメッセージを作成
        slack_message = {
            'username': 'Error Notifiy Bot',
            'channel': channel_id,
            'text': f"ログでエラーメッセージを検出しました。\n*ErrorLog Message*: \n{error_log_message}\n*CloudWatchLogs Log Group*: {log_group}\n*CloudWatchLogs Log Stream*: {log_stream}"
        }

        # Slack API にメッセージを送信
        req = urllib.request.Request(
            'https://slack.com/api/chat.postMessage',
            data=json.dumps(slack_message).encode('utf-8'),
            headers={
                'Content-Type': 'application/json',
                'Authorization': f'Bearer {slack_token}'
            }
        )

        try:
            with urllib.request.urlopen(req) as response:
                response_body = response.read()
                response_data = json.loads(response_body)

                # Slack API のレスポンスをチェック
                if not response_data.get("ok"):
                    error_message = response_data.get("error", "Unknown error")
                    raise Exception(f"Slack API error: {error_message}")
                
                print(f"Message posted to Slack: {response_body}")
        
        except Exception as e:
            print(f"Failed to send message to Slack: {e}")
            raise e  # Lambda 関数をエラーとして終了させる

    except Exception as e:
        print(f"Error processing CloudWatch Logs event: {e}")
        raise e

    return {
        'statusCode': 200,
        'body': json.dumps('Message sent to Slack')
    }

def lambda_handler(event, context):
  notify_slack(event, context)

SAM テンプレートの作成

template.yaml を以下のようにします。 Lambda 関数と、パラメーターで受け取ったCloudWatch Logsロググループに対してサブスクリプションフィルターを作成します

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: CloudWatch Logs Error to Slack Bot Notification using SSM Parameter Store

Parameters:
  SlackBotTokenParameter:
    Type: String
    Description: "The name of the SSM Parameter that stores the Slack Bot OAuth token"

  SlackChannelIdParameter:
    Type: String
    Description: "The name of the SSM Parameter that stores the Slack channel ID"

  CloudWatchLogsLogGroupName:
    Type: String
    Description: "The name of the CloudWatch Logs Log Group to monitor for errors"

Resources:
  SlackNotificationFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./src
      Handler: app.lambda_handler
      Runtime: python3.11
      MemorySize: 128
      Timeout: 10
      Policies:
        - AWSLambdaBasicExecutionRole
        - Statement:
            Effect: Allow
            Action:
              - ssm:GetParameter
              - ssm:GetParameters
            Resource: 
              - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${SlackBotTokenParameter}"
              - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter${SlackChannelIdParameter}"
      Environment:
        Variables:
          SLACK_BOT_TOKEN_PARAMETER: !Ref SlackBotTokenParam
          SLACK_CHANNEL_ID_PARAMETER: !Ref SlackChannelIdParam

  LogGroupSubscriptionFilter:
    Type: AWS::Logs::SubscriptionFilter
    Properties:
      LogGroupName: !Sub "${CloudWatchLogsLogGroupName}"
      # Error/ERROR にマッチするログをフィルタリング
      FilterPattern: ?ERROR ?Error ?error
      DestinationArn: !GetAtt SlackNotificationFunction.Arn

  LambdaInvokePermission:
    Type: 'AWS::Lambda::Permission'
    Properties:
      FunctionName: !GetAtt SlackNotificationFunction.Arn
      Action: 'lambda:InvokeFunction'
      Principal: 'logs.amazonaws.com'
      SourceArn: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:${CloudWatchLogsLogGroupName}:*"
      SourceAccount: !Ref 'AWS::AccountId'

Outputs:
  SlackNotificationFunctionArn:
    Description: "ARN of the Lambda function that sends notifications to Slack"
    Value: !GetAtt SlackNotificationFunction.Arn

samconfig.yaml の作成

以下のようなものにします。

parameter_overrides に Slack Botトークンと通知先SlackチャンネルのIDを保存したSSMパラメーターストアのキー名をセットします。

# More information about the configuration file can be found here:
# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
version = 0.1

[default]
[default.global.parameters]
stack_name = "CloudWatchLogsSlackNotifierSAMStack"

[default.build.parameters]
cached = true
parallel = true

[default.validate.parameters]
lint = true

[default.deploy.parameters]
capabilities = "CAPABILITY_IAM"
confirm_changeset = true
resolve_s3 = true
s3_prefix = "CloudWatchLogsSlackNotifierSAMStack"
region = "ap-northeast-1"
parameter_overrides = "SlackBotTokenParam=\"/slack/bot/token/notify-error\" SlackChannelIdParam=\"/slack/channel/id/notify-error\""


[default.package.parameters]
resolve_s3 = true

[default.sync.parameters]
watch = true

[default.local_start_api.parameters]
warm_containers = "EAGER"

[default.local_start_lambda.parameters]
warm_containers = "EAGER"

デプロイ

デプロイして動作を確認します

% sam deploy

まとめ

CloudWatch Logs へ特定の文字列が出力された場合に, CloudWatch Logs のサブスクリプションフィルターで検知して、それを起点に Slack に通知させる Lambda を実行して通知するための AWS SAM アプリケーションのサンプルについて簡単に書きました。

CloudWatch Logsロググループやパラメーターストアへの登録が必要なので、これ単発では動かせないですが、CloudWatch Logsを使っている場合のログ監視の仕組みとしてたまに必要になるので簡単なメモとして記載しました。




以上の内容はhttps://dshimizu.hatenablog.com/entry/2025/03/31/002652より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14