約1年前の記事 CloudWatchLogsのログ監視 - サブスクリプションフィルタ + Lambdaでメール送信(2) の改良版の話。
- ログ監視用コードはループさせない
上記投稿を書いた当時はログイベント全てをメール通知する方式だったが、これだと類似の通知メールが大量に飛んでしまい、抑止した方がいいのでは…という流れになった。通知は最初の一回だけ、に変更した。運用上はこれで要件は満たせるという判断。これが正しいということではなく、現場の方針次第。以下記事のコメント欄で指摘されている対応は、逆にやめることになった。
cloudwatchlogs -> lambda -> SNSを試してみた
- Lambda自体のログにトリガー元を記録する
別途Lambda自体のログについて。Lambda自体のログはエラーが出たか否かと発生時刻くらいしか情報を吐かない。設計上複数のイベントを一つのLambda関数に連携する想定なので、そのままだとLambda関数のInvoke時のトリガーが不明で調査が困難になる(ログ監視でもリソース、メトリクス監視でも)
そのため、Lambdaコード自体のログに、トリガー元のロググループ名を出力させることにした。(アラームならアラーム名)
メッセージ全量を吐かせてもいいがそうするとLambda自体に問題がなくてもLambdaのログにErrorなどの文字が含まれてしまい、ややこしくなる。ログも肥大化する。(大した量ではないとはいえ)このためメッセージ全量については「普段はコメントしておいて、デバッグ時のみコメントを外す」運用とした。
追加した内容
デバッグ用に、メッセージ全量出力。必要時だけコメントを外す
log_message= log_json['message']
#print('LogMessage: ', log_message)
ロググループ名を出力。これは常時適用
log_group = data_json['logGroup']
print('LogGroup: ',log_group)
アラーム用コード例。件名に投入するためアラーム名を取得しているが、printでLambda自体のログにも吐かせる。
alm_list = alarm[0].split(':')
alm_name = alm_list[-1]
print('AlarmName: ', alm_name)
コード全体。
修正前
import base64
import json
import zlib
import datetime
import os
import boto3
from botocore.exceptions import ClientError
print('Loading function')
def lambda_handler(event, context):
data = zlib.decompress(base64.b64decode(event['awslogs']['data']), 16+zlib.MAX_WBITS)
data_json = json.loads(data)
log_entire_json = json.loads(json.dumps(data_json["logEvents"], ensure_ascii=False))
log_entire_len = len(log_entire_json)
print(log_entire_json)
for i in range(log_entire_len):
# ロググループ名取得
log_group = data_json['logGroup']
# ログストリーム名取得
log_stm = data_json['logStream']
# LogEvents取得
log_json = json.loads(json.dumps(data_json["logEvents"][i], ensure_ascii=False))
#UNIX時間→時刻/JST変換
datetime_utc = log_json['timestamp'] / 1000.0
datetime_utc = datetime.datetime.fromtimestamp(datetime_utc).strftime('%Y/%m/%d %H:%M:%S')
datetime_utc = datetime.datetime.strptime(datetime_utc, '%Y/%m/%d %H:%M:%S')
datetime_jst = datetime_utc + datetime.timedelta(hours = 9)
# メール件名整形
subject_str = "本番環境 - ログアラート " + log_group
# メッセージ本文整形
fix_msg = "以下のアラートが発生しました" + "\n"
log_group_msg = "ロググループ名:" + "\n" + log_group
log_stm_msg = "ログストリーム名:" + "\n" + log_stm
time_msg = "発生時刻:" + "\n" + str(datetime_jst)
log_msg = "メッセージ:" + "\n" + log_json['message']
msg = fix_msg + "\n\n" + log_group_msg + "\n\n" + log_stm_msg + "\n\n" + time_msg + "\n\n" + log_msg
try:
sns = boto3.client('sns')
#SNS Publish
publishResponse = sns.publish(
TopicArn = os.environ['SNS_TOPIC_ARN'],
Message = msg,
Subject = subject_str
)
except Exception as e:
print(e)
修正後
import base64
import json
import zlib
import datetime
import os
import boto3
from botocore.exceptions import ClientError
print('Loading function')
def lambda_handler(event, context):
# ログメッセージ定義
data = zlib.decompress(base64.b64decode(event['awslogs']['data']), 16+zlib.MAX_WBITS)
data_json = json.loads(data)
log_entire_json = json.loads(json.dumps(data_json["logEvents"], ensure_ascii=False))
log_entire_len = len(log_entire_json)
# ログイベント全体を出力(デバッグ用)
#print(log_entire_json)
# ロググループ名取得
log_group = data_json['logGroup']
print('LogGroup: ',log_group)
# ログストリーム名取得
log_stm = data_json['logStream']
# LogEvents取得。ログイベント数に関わらず最初の1ログイベントのみ取得する。(大量通知抑止のため)
log_json = json.loads(json.dumps(data_json["logEvents"][0], ensure_ascii=False))
#UNIX時間→時刻/JST変換
datetime_utc = log_json['timestamp'] / 1000.0
datetime_utc = datetime.datetime.fromtimestamp(datetime_utc).strftime('%Y/%m/%d %H:%M:%S')
datetime_utc = datetime.datetime.strptime(datetime_utc, '%Y/%m/%d %H:%M:%S')
datetime_jst = datetime_utc + datetime.timedelta(hours = 9)
# ログメッセージ取得
log_message = log_json['message']
#print('LogMessage: ', log_message)
# メール件名整形
subject_str = "本番環境 - ログアラート " + log_group
# メッセージ本文整形
fix_msg = "以下のアラートが発生しました" + "\n"
log_group_msg = "ロググループ名:" + "\n" + log_group
log_stm_msg = "ログストリーム名:" + "\n" + log_stm
time_msg = "発生時刻:" + "\n" + str(datetime_jst)
log_msg = "メッセージ:" + "\n" + log_json['message']
msg = fix_msg + "\n\n" + log_group_msg + "\n\n" + log_stm_msg + "\n\n" + time_msg + "\n\n" + log_msg
try:
sns = boto3.client('sns')
#SNS Publish
publishResponse = sns.publish(
TopicArn = os.environ['SNS_TOPIC_ARN'],
Message = msg,
Subject = subject_str
)
except Exception as e:
print(e)