LoginSignup
2
1

More than 3 years have passed since last update.

CloudWatch Logs Insights でURLのパスとクエリを分けて取得する

Posted at
255.255.255.255 - - [01/Apr/2021:11:28:40 +0900] "GET /api/hoge/fuga HTTP/1.1" 200 246 "https://example.com/hoge" "Mozilla/5.0 (Example User Agent)" "255.255.255.255"
255.255.255.255 - - [01/Apr/2021:11:28:41 +0900] "GET /api/foo?bar=xxx&piyo=zzz HTTP/1.1" 200 84 "https://example.com/hoge" "Mozilla/5.0 (Example User Agent)" "255.255.255.255"

こんな感じの nginx のアクセスログがあって、CloudWatchLogsに転送されてるときに、
パス毎のアクセス数を集計したいことがあります。

これをCloudWatch Logs Insightsで分析しようとすると、

fields @timestamp, @message
| parse '* - - [* *] "* * *" * * "*" "*" "*' as ip1, dateTime, timeZone, method, uri, httpVer, status, bytes, referral, userAgent, ip2
| stats COUNT() BY uri

みたいな書き方が多いと思うんですが、そうすると、

uri                        COUNT()
/api/hoge/fuga                  30
/api/foo?bar=xxx&piyo=xxx        2
/api/foo?bar=xxx&piyo=yyy        4
/api/foo?bar=yyy&piyo=zzz        1
...

みたいな感じで、クエリパラメータが違うと別パス扱いになるのでなんか違う感じになります。
ほしい結果としては以下みたいな感じで、 /api/foo は1つにまとめたい感じ。

uri                        COUNT()
/api/hoge/fuga                  30
/api/foo                        16

で、CloudWatch Logs Insightsのクエリだけでどうすればいいかというと、
parse のときに、正規表現を使うようにします。

ドキュメントを見ると以下のように書いてあって、正規表現を利用したパターンマッチが使えます。

この単一のログ行を例として使用します。

25 May 2019 10:24:39,474 [ERROR] {foo=2, bar=data} The error was: DataIntegrityException

次の 2 つの parse 式は、それぞれ以下のことを行います。エフェメラルフィールド level、config、および exception が作成されます。level の値は ERROR、config の値は {foo=2, bar=data}、exception の値は DataIntegrityException です。最初の例は glob 式を使用し、2 番目の式は正規表現を使用します。

parse @message "[*] * The error was: *" as level, config, exception

parse @message /\[(?<level>\S+)\]\s+(?<config>\{.*\})\s+The error was: (?<exception>\S+)/

次の例では、正規表現を使用して、ログフィールド @message から、エフェメラルフィールド user2、method2、および latency2 を抽出し、method2 と user2 の一意的な組み合わせごとに平均レイテンシーを返します。

parse @message /user=(?<user2>.*?), method:(?<method2>.*?), latency := (?<latency2>.*?)/
| stats avg(latency2) by method2, user2

というわけで、正規表現でパースすることで、パスとクエリをそれぞれ取得することができます。

fields @timestamp, @message
| parse @message /[0-9.]+ - - \[\S+ \S+\] \"(?<method>[A-Z]+) (?<path>[^?]+)\??(?<query>\S+)? \S+" (?<status>[0-9]+) \S+ "\S+" "[^"]+" "\S+"/
| stats COUNT() BY path

これで、期待する結果が得られます。

path                       COUNT()
/api/hoge/fuga                  30
/api/foo                        16

正規表現の場合の書式は、 (?<エイリアス>パターン) みたいな感じで名前付きの値を取得できるので、

(?<path>[^?]+)\??(?<query>\S+)?

っていう感じで、パスと、クエリ文字列があればクエリを取得する感じにできます。
通常の正規表現として考えると、([^?]+)\??(\S+)? になり、 URIの中の ? の前と後でそれぞれマッチングするようにしてるだけです。

split() 関数みたいなので分割できるとか、 正規表現で置換できるとかあればいいんですが、あんまり複雑な関数は用意されてないので、将来的にそのへんが整備されたらもっと楽にできるようになるんじゃないかなと思ってます。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1