はじめに
今回、CloudWatchのログをSlack通知する実装をしました。
こんなに簡単に実装できるんだ〜という感じでしたが、色んなサービスが連携して面白かったので記録しておきます。
やりたいこと:
CloudWatchに溜められるlogの中に、FATAL、ERROR、WARNINGの情報がある場合、slackのログ監視用チャンネルにログの内容を出力する
前提:
・slack通知したいログがCloudWatchに出力されている
・slack通知用のアプリを作成している
・slack通知用のチャンネルを用意している(アプリをインテグレーションしている)
CloudWatchのログをSlack通知する全体像
【参考】
ログデータの流れまとめ
- AWS CloudWatchの特定のロググループに出力される
- サブスクリプションフィルターのパターンに一致した場合、紐づけたLambda関数に送られる
- Lambdaの中で整形される
- slackにポストされる
実装手順
1. ログをslackにポストする用のLambda関数を作成
2. 出力対象ログが属するロググループにサブスクリプションフィルターを設定
・ログの送信先として、先ほど作成したLambda関数を選択する
・サブスクリプションフィルターパターンにFATAL、ERROR、WARNINGをフィルタするように設定する
※CloudWatchに出力されているログの形に合わせてパターンを設定する
例:JSON形式でログが出力されている場合のパターン↓
{ ( $.level = "FATAL" ) || ( $.level = "WARN" ) || ( $.level = "ERROR" ) ) }
3. System Manegerのパラメータストアでパラメータを新規作成し、slackアプリのトークンを保存
4. LambdaのIAMロールにSSM読み取り用ポリシー追加
※System Manegerのパラメータストアに登録した内容を取り出すために、Lambdaに紐づくIAMロールにポリシー(iam-ssm-get-parameter)を追加
5. Lambdaの中身を書いていく
・ログの情報を取り出す
・メッセージを整形
・postMessageする
require 'json'
require 'base64'
require 'zlib'
require 'aws-sdk-ssm'
require 'aws-sdk-kms'
require 'slack-ruby-client'
def lambda_handler(event:, context:)
data = JSON.parse(Zlib::GzipReader.new(StringIO.new(Base64.decode64(event["awslogs"]["data"]))).read)
# 集計するログの基本情報
log_group = data["logGroup"]
log_stream = data["logStream"]
log_events = data["logEvents"]
# ログの基本情報
base_message = "■対象ロググループ:" + log_group + " > " + log_stream + "\n"
# ログの本文
logs = []
log_events.each { |log_event|
logs_json = JSON.parse(log_event["message"])
logs.push(logs_json)
}
# slackに送信
post_slack(base_message, logs)
end
#
# slackに送信
#
def post_slack(base_message, logs)
# 送信するテキスト:ログの基本情報
text = "<!here>\n"
text += base_message
# アタッチメントテキスト:ログの内容
attachments = []
attachment_texts = []
logs.each do |log_json|
timestamp = log_json["time"]
attachment_text = "■発生時刻: " + timestamp + "\n"
attachment_text += "■ログメッセージ: " + message + "\n"
attachment_texts.push(attachment_text)
end
attachments.push({ fallback: 'Error', text: attachment_texts.join("\n\n"), color: 'danger' })
slack_client.chat_postMessage(channel: 'チャンネル名', text: text, attachments: attachments, as_user: true)
end
#
# slackクライアント
#
def get_slack_client
ssm_client = Aws::SSM::Client.new
get_token_request = {
name: 'xxx', # パラメータ名
with_decryption: true # 暗号化されている場合は復号し、暗号化されていない場合は何もしない
}
Slack.configure do |config|
config.token = ssm_client.get_parameter(get_token_request).parameter.value
end
Slack::Web::Client.new
end
最後に
最近AWSを触り始めた初学者ですが、AWSについてもっと知りたい欲が出てきました。
また、面白いことがあれば記事にしたいと思います!