こんにちは、テクノロジーグループの矢田です。
我が家には猫が一匹おりまして、毎日のように窓から外を眺めて鳥が飛んでいるのを監視しています。今日も捗ってるな〜と思いながらそんな猫を眺めて仕事しているのですが、今日は監視の話をしたいと思います。
クラシコムでは「北欧、暮らしの道具店」の監視を行うためにMackerelを利用しています。
MackerelではサーバーのCPU・メモリなどのメトリック監視や外形監視、サービスメトリックを送っての監視など多方からサービスの状態を監視することができます。
今回は先日α版が公開されたMackerelのcloudwatch logs aggregatorについて紹介したいと思います。
このモジュールではアプリケーションからAWS CloudWatch Logsに吐き出したログをメトリック化しMackerelに送ることでアプリケーションログをメトリックとして監視することができます。
以前、クラシコムではバリデーションエラーをSentryにレポートするようにしておりました。そもそもユーザーの入力エラーなのでシステム自体のエラーではないため全部拾う必要はないと考えておりましたが、ユーザーだけでなくシステムのエラーで急増することがないかが心配でSentryにレポートし数が少ないうちはignoreする設定を入れていました。
しかし、SentryのignoreがSentryまかせなのでたまに通知が来てしまうことがあり狼少年になりがちなのでなんとかしたいね〜という話がでたころにcloudwatch logs aggregatorが発表されたのです!
早速使い方を紹介しようと思います。
用意するもの
- AWSアカウント
- Terraform環境
- CloudWatch Logsのロググループ
- MACKEREL_APIKEYを作成しSSMのパラメタストアに登録しておく
CloudWatch Logs Insightsでクエリを作る
cloudwatch logs aggregatorではLogs Insightsのクエリを利用しログを集計することができます。
まず集計したいアプリケーションログの例を作ります。
[01-Feb-2022 17:53:28] WARNING: [pool www] child 53 said into stderr: "{"message":"Validation Failed","context":{"exception":{"class":"ValidationFailedException","message":"Validation failed"}},"level":200,"level_name":"INFO","channel":"development","datetime":{"date":"2022-02-01 17:53:28.845768"},"extra":[]}"
次にこのログを集められるようなクエリを作ります。
クエリはAWS Management ConsoleからCloudWatch Logs Insightsのサービス内で実際のロググループをターゲットにしてクエリを試してみて作成しました。
fields @timestamp, @message, "count"
| filter @message like "ValidationFailedException"
| stats count() as `~count` by count
| display `~count`
今回集計したいログは ValidationFailedException という文字列を含んでいます。cloudwatch logs aggregatorでは文字列ではなく数字を渡してあげる必要があるので、count() した値を渡しています。
このログでは、parse するのがなかなか大変で @messageや @timestamp で集計が難しかったため、単純に ValidationFailedExcepitoin を含む行をgrepして count という文字列のカラムを足してあげて count カラムを count() することで行数を計算しています。
Terraformでモジュールを利用する
mackerel cloudwatch logs aggregatorを利用するためにterraformのコードを作成します。
まずLambdaを作成します。このLambdaがログをMackerelに送ってくれるようです。
module "cw_logs_aggregator_lambda" {
source = "github.com/mackerelio-labs/mackerel-monitoring-modules//cloudwatch-logs-aggregator/lambda?ref=v0.1.2"
region = "ap-northeast-1"
}
次にEventBridgeのevent ruleを作成します。このルールがキックされることでLambdaが定期的に実行されるようになります。
module "cw_logs_aggregator_rule_validation_failed" {
source = "github.com/mackerelio-labs/mackerel-monitoring-modules//cloudwatch-logs-aggregator/rule?ref=v0.1.2"
region = "ap-northeast-1"
rule_name = "mackerel-cwl-aggregator-validation-failed"
function_arn = module.cw_logs_aggregator_lambda.function_arn
# Mackerel settings
api_key_name = "/path/to/ssm/MACKEREL_APIKEY"
service_name = "Mackerelのサービス名"
# Query settings
log_group_name = "ロググループ名"
query = <<QUERY
fields @timestamp, @message, "count"
| filter @message like "ValidationFailedException"
| stats count() as `~count` by count
| display `~count`
QUERY
# グラフ名
metric_name_prefix = "validation-failed" # グラフのデフォルト値
default_metrics = {
"validation-failed.count" : 0
}
schedule_expression = "rate(5 minutes)"
interval_in_minutes = 5
offset_in_minutes = 10
}
この場合5分に1回メトリックがMackerelに送信されます。
グラフのデフォルト値を指定していない場合、グラフが途切れ途切れになったりLambdaのエラーが出たり(下記)していたので設定をしておいた方が良さそうです。
{
"aws_request_id": "231ab7b4-ec4f-4589-8873-65f1ba65c30b",
"level": "error",
"msg": "failed to post metric data: API request failed: Invalid Parameter: obj error.expected.jsarray.",
"rule_name": "mackerel-cwl-aggregator-validation-failed",
"time": "2022-01-17T05:14:54Z"
}
# グラフのデフォルト値
default_metrics = {
"validation-failed.count" : 0
}
集計のクエリでは ~count を表示するようにしていますが、このチルダ(~)は無視されて count としてメトリックが送られるので、デフォルト値は count としています。
2つのモジュールを追加し terraform apply をすると下記のAWSリソースが作成されます。
- aws_cloudwatch_log_group
- aws_iam_role : Lambda用
- aws_lambda_function
- aws_lambda_permission
- aws_cloudwatch_event_rule
- aws_cloudwatch_event_target
これでリソースの作成は完了です!
メトリックの投稿
実際にロググループにログを落としてグラフが動作するかを確認します。CloudWatch Logsではログイベントを手動で追加することができます。
CloudWatch Logs > ロググループ > ロググループの選択 > アクションから「ログイベントの作成」を選択します。
テキストボックスに集計したいログの例を入れて「作成」をおします。
ログイベントの作成
ログの追加
Mackerelのグラフは Services > [指定したサービス] > サービスメトリック に作成されます。
余談ですが、CloudWatch LogsではLambdaのログとそれ以外のログとでparseされ方が違うらしく、ちょっとハマりました。JSONログは基本きれいにparseしてくれるのですが、私が対象にしていたログがログの中にJSONが含まれるもので、Lambdaのロググループだとそれでもいい感じにparseしてくれるようなのですが、それ以外のロググループはちゃんとしたJSONじゃないとJSONとして認識してくれませんでした。
CloudWatch Logs Insights は、Lambda ログのログフィールドを自動的に検出します。ただし、各ログイベントに埋め込まれている最初の JSON フラグメントのみが検出されます。Lambda ログイベントに複数の JSON フラグメントが含まれている場合は、`parse`コマンドを使用してログフィールドを解析して抽出できます。詳細については、「JSON ログのフィールド」を参照してください。
https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData-discoverable-fields.html
ログの監視
メトリックを送るだけでは異常があったときに気づけないのである程度メトリックが溜まった後は監視を作成します。
ありがたいことにMackerelはTerraform Providerがあるのでそちらを利用します。
terraform {
required_providers {
mackerel = {
source = "mackerelio-labs/mackerel"
version = "0.0.6"
}
}
backend "s3" {
# 設定を追加
}
}
resource "mackerel_monitor" "high_validation_failed" {
name = "High validation failed"
service_metric {
service = "サービス名"
metric = "validation_failed.count"
operator = ">"
warning = 30 # グラフからいい感じの値を入れる
duration = 1
max_check_attempts = 2 # 10分間に60回失敗していたら通知
}
}
これでバリデーションエラーが急増しているのを通知することができるようになりました。
導入しての感想
今回はバリデーションエラーを例にMackerel cloudwatch logs aggregatorの導入方法を紹介させていただきました。
詰まりどころとしてはLogs Insightsのクエリの作成とデフォルト値の設定周りかなあと思います。それ以外は意外と簡単に導入することができました。特にコードの追加も多くなく、サクッと導入できる印象です。
以前Mackerel cloudwatch logs aggregatorの導入セミナーを受講させていただいたのですが、EventBridgeのリソースを複数作成して一つのLambdaでメトリックを送信することができるそうなので、別のログを集計するのも簡単に導入できそうです。
他にも集計してみたいログがあるので、今後はアプリケーション周りの監視ももっと進めていけたらと考えております。
ちょうどこのブログを書き始めた頃にMackerelの公式から詳しい導入方法の記事がでたのでこちらも是非参考になさってください。
クラシコムでは監視をもっと整備したい!インフラもアプリケーションも触ってみたい!エンジニアを募集しています。もし興味がありましたらぜひ一度お話しできればと思っております。