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
以下を許可しておきます。
- https://api.slack.com/scopes/calls:read
- calls:write permission scope | Slack
- chat:write permission scope | Slack
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を使っている場合のログ監視の仕組みとしてたまに必要になるので簡単なメモとして記載しました。