はじめに
エラーログ(今回はlaravelのログ)出力時、CloudWatch Logsサブスクリプションフィルタを使用し、slackに通知する仕組みを作ります。
CloudWatch Logsの収集対象としているログにErrorという文字列が出力されたらLambdaが起動し、Slackに通知します。
事前に準備
・EC2サーバーでwebページを構築済み
・統合 CloudWatch agent についての理解
統合 CloudWatch agentとは、EC2などのサーバーにインストールする CloudWatchのagent (パッケージ) のことです。
インストールすることでカスタムメトリクスが取得できるようになったり、CloudWatch Logs にログを送信できるようになります。
流れ
①EC2アタッチ用IAMロールの作成
②EC2にCloudWatch agentをインストール
③CloudWatch agent設定ファイルの作成
④CloudWatchエージェントの起動
⑤SlackのWebhookURLを作成
⑥Lambda作成
⑦CloudWatch Logsサブスクリプションフィルタを設定
①~④までは今回、ssmを使用した方法ですが、ssmを使用せず、下記の記事のec2内で設定する方法もあります。
EC2アタッチ用IAMロール作成
1.下記のポリシーを付与したIAMロールを作成します。
・AmazonSSMManagedInstanceCore(Systems ManagerでCloudWatchエージェントをインストール、起動する際に必要)
・CloudWatchAgentAdminPolicy(Systems Manager パラメータストアに書き込むために必要)
2.作成後、EC2にアタッチします。
EC2コンソール画面→EC2を選択しアクション→セキュリティ→IAMロールの変更からアタッチできます。
EC2にCloudWatch agentをインストール
CloudWatch agentをインストールする方法は、2パターンあります。それぞれの方法を解説します
①Systems Managerを使用してインストール
②EC2にSSH接続してインストール
①Systems Managerを使用してインストール
1.Systems Managerコンソールを開き、Runcommandを選択
2.AWS-ConfigureAWSPackageを選択
3.コマンドパラメータは以下を選択する
Action:Install
Installation Type:Uninstall and reinstall
Name: AmazonCloudWatchAgent
4.ターゲットは、インスタンスを手動で選択する→EC2インスタンスを選択します。
注意:ターゲットのインスタンスが出てこない場合
EC2インスタンスにIAAMロールをアタッチして反映されるまでに5分程度かかることがありますので、お待ち下さい。それでも反映されない場合、②EC2にSSH接続してインストール
の方法をためしてください。
5.ほかは、デフォルトのまま実行をクリックします。(S3 バケットへの書き込みを有効化はしなくても構いません)
6.コマンドのステータスが成功しているか確認します。
②EC2にSSH接続してインストール
EC2インスタンスへSSH接続して、下記コマンドでパッケージをダウンロードして、その後インストールします。
リージョンが東京でなければ、コマンドのap-northeast-1
の箇所を変更してください
$ wget https://s3.ap-northeast-1.amazonaws.com/amazoncloudwatch-agent-ap-northeast-1/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
$ sudo rpm -U ./amazon-cloudwatch-agent.rpm
CloudWatch agent設定ファイルの作成
EC2インスタンスへSSH接続して、下記コマンドで設定ファイルの作成します。質問に答える形式です。
default choice:
と同じ選択肢であれば、Enterキーを押すだけでよいです。
$ sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-config-wizard
Amazon Linux2を使用していますので、1を選択します
On which OS are you planning to use the agent?
1. linux
2. windows
default choice: [1]:
CloudWatchエージェントはrootで動かします
Which user are you planning to run the agent?
1. root
2. cwagent
3. others
default choice: [1]:
メトリクスは必要ではありません、ログを収集するので下記の質問は全て2を選択。
Do you want to turn on StatsD daemon?
1. yes
2. no
default choice: [1]:
2
Do you want to monitor metrics from CollectD?
1. yes
2. no
default choice: [1]:
2
Do you want to monitor any host metrics? e.g. CPU, memory, etc.
1. yes
2. no
default choice: [1]:
2
Do you have any existing CloudWatch Log Agent (http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AgentReference.html) configuration file to import for migration?
1. yes
2. no
default choice: [2]:
2
Laravelのログのパス、ロググループ、ログストリーム名を指定します。
今回は下記のようにしました。ログのパスは、自身の環境に合わせてください。
ログのパス/home/ec2-user/logs/laravel*.log
ロググループlaravel-logs
ログストリーム名{instance_id}
Do you want to monitor any log files?
1. yes
2. no
default choice: [1]:
1
Log file path:
/home/ec2-user/logs/laravel*.log
Log group name:
default choice: [laravel*.log]
laravel-logs
Log stream name:
default choice: [{instance_id}]
{instance_id}
追加でログ収集しないので2を選択します。
Do you want to specify any additional log files to monitor?
1. yes
2. no
default choice: [1]:
2
設定ファイルは作成されますが、 Systems Manager parameter storeで設定ファイルを管理するので1を指定します。
Edit it manually if needed.
Do you want to store the config in the SSM parameter store?
1. yes
2. no
default choice: [1]:
1
パラメータストア名を設定します。
AmazonCloudWatch-で始まる名前にする必要があるので注意してください。
今回はAmazonCloudWatch-laravel-logsとしました。
What parameter store name do you want to use to store your config? (Use 'AmazonCloudWatch-' prefix if you use our managed AWS policy)
default choice: [AmazonCloudWatch-linux]
AmazonCloudWatch-laravel-logs
リージョン、クレデンシャル情報はデフォルトを使用します。
クレデンシャル情報は、IAMコンソール画面→ユーザー選択→認証情報タブ→アクセスキーで確認できます。
表示されているアクセスキーと異なれば、アクセスキーを作成し、クレデンシャル情報は2. Other
を選択します。
Trying to fetch the default region based on ec2 metadata...
Which region do you want to store the config in the parameter store?
default choice: [ap-northeast-1]
Which AWS credential should be used to send json config to parameter store?
1. XXXXXXXXXXXXXXXXXXXX(From SDK)
2. Other
default choice: [1]:
Please make sure the creds you used have the right permissions configured for SSM access.
Which AWS credential should be used to send json config to parameter store?
1. XXXXXXXXXXXXXXXXXXXX(From SDK)
2. Other
default choice: [1]:
Successfully put config to parameter store AmazonCloudWatch-laravel-logs.
Program exits now.
設定ファイルが作成され、Systems Manager parameter storeに保存されました。
今回は、詳細なメトリクスは設定しませんでしたが、CloudWatchでEC2のメモリ・ディスク使用率を監視する場合、こちらの記事で、設定ファイルを作成できます。
その際は、EC2にcollectd (サーバの統計情報を収集するためのソフトウェア) のアプリケーションが必要ですので、インストールする必要があります。
sudo yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
sudo yum install -y collectd
CloudWatchエージェントの起動
SSMのRunCommandでエージェントを起動します。
1.Systems Managerコンソールを開き、Runcommandを選択
2.コマンドドキュメントリストでAmazonCloudWatch-ManageAgentを選択
3.コマンドパラメータは、以下を選択
Action: configure
Mode: ec2
Optional Configuration Source:ssm
Optional Configuration Location:AmazonCloudWatch-laravel-logs(パラメータストア名を指定)
Optional Open Telemetry Collector Configuration Source:ssm
Optional Open Telemetry Collector Configuration Location:空白
Optional Restart: yes
4.ターゲットは、インスタンスを手動で選択する→EC2インスタンスを選択します。
5.ほかは、デフォルトのまま実行をクリックします。(S3 バケットへの書き込みを有効化はしなくても構いません)
6.コマンドのステータスが成功しているか確認します。
7.Cloudwatch Logsグループにlaravel-logs
が作成されていることを確認します
反映に数分かかることがあります。それでも確認されない場合、サーバー内のlogファイルに書き込みをしてみてください。
Slack通知したいエラーログのパスが複数ある場合
複数のログをCloudWatchLogsに出力したい場合、マイパラメータを編集する必要があります。
マイパラメータの名前をクリックします。
編集をクリックします。
{
"agent": {
"run_as_user": "root"
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/home/ec2-user/logs/laravel*.log",
"log_group_name": "laravel-logs",
"log_stream_name": "{instance_id}"
}
]
}
}
}
}
collect_list
の[]内に複数入れることで、ログを複数CloudWatchLogsに出力されます。
{
"agent": {
"run_as_user": "root"
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/home/ec2-user/logs/laravel*.log",
"log_group_name": "laravel-logs",
"log_stream_name": "{instance_id}"
},
{
"file_path": "/batch/logs/*.log",
"log_group_name": "batch-logs",
"log_stream_name": "{instance_id}"
}
]
}
}
}
}
メトリクスも入れる場合
{
"agent": {
"run_as_user": "root"
},
"logs": {
"logs_collected": {
"files": {
"collect_list": [
{
"file_path": "/home/ec2-user/logs/laravel*.log",
"log_group_name": "laravel-logs",
"log_stream_name": "{instance_id}"
},
{
"file_path": "/batch/logs/*.log",
"log_group_name": "batch-logs",
"log_stream_name": "{instance_id}"
}
]
}
}
},
"metrics": {
"append_dimensions": {
"AutoScalingGroupName": "${aws:AutoScalingGroupName}",
"ImageId": "${aws:ImageId}",
"InstanceId": "${aws:InstanceId}",
"InstanceType": "${aws:InstanceType}"
},
"metrics_collected": {
"collectd": {
"metrics_aggregation_interval": 60
},
"disk": {
"measurement": ["used_percent"],
"metrics_collection_interval": 60,
"resources": ["*"]
},
"mem": {
"measurement": ["mem_used_percent"],
"metrics_collection_interval": 60
},
"statsd": {
"metrics_aggregation_interval": 60,
"metrics_collection_interval": 10,
"service_address": ":8125"
}
}
}
}
編集後のパラメータを使い、RunCommandで再度実行すると、パス/batch/logs/*.log
もCloudWatchLogsに出力されます。
SlackのWebhookURLを作成
1.slackのAppを選択し、Incoming WebHooks
を追加します
2.Webページに遷移し、Slackに追加をクリックします
3.通知したチャンネル名を選択し、Incoming WebHooks
を追加をクリックします
4.チャンネルへの投稿を選択し、Webhook URL
をコピーし、設定を保存します
5.設定を保存すると、チャンネルに下記画像のメッセージが表示されます。
Lambda作成
-
下記の設定でLambda作成
・タイプ:一から作成
・関数名:Laravel-ErrorLogs-to-Slack
・ランタイム:python3.8 -
設定→環境変数を設定します
- キー:
ProjectName
、値:HOGE
にします。 - キー:
WebhookURL
、値:webhookのURL
にします。
コード内でos.environ['WebhookURL']
とすると値を参照できます。
import json
import logging
import os
import base64
import gzip
import urllib.request
HOOK_URL = os.environ['WebhookURL']
PROJECT_NAME = os.environ['ProjectName']
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# CloudWatchLogsからのデータはbase64エンコードされているのでデコード
decoded_data = base64.b64decode(event['awslogs']['data'])
# バイナリに圧縮されているため展開
json_data = json.loads(gzip.decompress(decoded_data))
# CloudWatch Logsに複合化&解凍したログを出力
logger.info("Event: " + json.dumps(json_data))
error_message = json_data['logEvents'][0]['message']
logger.info("Message: " + str(error_message))
# 環境ごとに環境名を記載。logGroupはロググループ名を記載。
if json_data['logGroup'] == "laravel-logs":
environment = "本番"
elif json_data['logGroup'] == "laravel-logs-dev":
environment = "開発"
# Slackへのメッセージを作成
send_data = {
"type": "mrkdwn",
"text": "<!channel> %s Laravel %s環境:exclamation::exclamation:\nエラーログを検出しました。ログを確認してください。\n ```%s``` \n確認中は:eyes:、完了後は:white_check_mark:をお願いします" % (PROJECT_NAME, environment, error_message)
}
send_text = json.dumps(send_data)
request = urllib.request.Request(
HOOK_URL,
data=send_text.encode('utf-8'),
method="POST"
)
with urllib.request.urlopen(request) as response:
response_body = response.read().decode('utf-8')
下記は、本番と開発環境でロググループの名前を分けている場合に、使用するとよいでしょう。
なければ削除してよいです。
# 環境ごとに環境名を記載。logGroupはロググループ名を記載。
if json_data['logGroup'] == "laravel-logs":
environment = "本番"
elif json_data['logGroup'] == "laravel-logs-dev":
environment = "開発"
ログデータの取得については、こちらを参照してください。
AWS Lambda のサブスクリプションフィルターのevent構造
メッセージは、絵文字チートシートと、Slack Fromatting text(View this exampleをクリックすると、Block Kit Builder
に遷移して、メッセージを調整します。)を参考にしました。
Block Kit Builder
でメッセージを作成したら、右側のコードをコピーします。
ただし、コピーしたコードの"blocks"
の箇所を削除してから、Lambdaにコードを貼り付けます。
Block Kit Builderで作成時
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<!channel> Laravel 本番環境:exclamation::exclamation:\nLaravelのエラーログを検出しました。ログを確認してください。"
}
}
]
}
Lambdaに貼り付けるコード
{
"type": "mrkdwn",
"text": "<!channel> Laravel 本番環境:exclamation::exclamation:\nLaravelのエラーログを検出しました。ログを確認してください。\n ```%s``` " % (error_message)
}
エラーログの箇所のみをマークダウンする場合、\n ```%s``` " % (error_message)
を付け加えます。
CloudWatch Logsサブスクリプションフィルタを設定
1.CloudWatch Logsのロググループlaravel logs
を選択します。
タブのサブスクリプションフィルターから、Lambda サブスクリプションフィルターを作成
をクリックします。
2.下記の通り設定します。
・送信先:先程作成したLambda
・ログの形式:その他
・フィルターパターン:?ERROR ?error
(slackに通知したい文字を選択します。複数選択するときは、半角スペースを開けます。全角スペースはだめです。)
・サブスクリプションフィルター名:お好きな名前
3.パターンをテストできます。問題なければストリーミング開始
をクリック
動作確認
laravelのログファイルにエラー文を加えると、以下の通り、slackに通知が来ました。
ログデータは下記のようなデータとなります。
{
"messageType": "DATA_MESSAGE",
"owner": "658663129401",
"logGroup": "laravel-logs",
"logStream": "i-0000000000000",
"subscriptionFilters": [
"ERROR"
],
"logEvents": [
{
"id": "36722748203695186877044706505754336186020163251740803078",
"timestamp": 1646704981236,
"message": "[2021-01-11 11:11:11] production.ERROR: エラーテストです。"
}
]
}
参考
・実行中の EC2 Linux インスタンスに CloudWatch Logs エージェントをインストールして設定する
・CloudWatch LogsでLaravelのログを収集してみた
・【CloudWatch・サブスクリプションフィルタ・Slack】出力されるログの一部を抽出してSlackにわかりやすいメッセージ出力してみた
・AWSで監視してアラートをメール送信する(4選)
・Emoji cheat Sheet(lambda用)
・AWS CloudWatchのログフィルタパターンを分かりやすく解説