【Azure】これ知ってた? 「Log Analytics」「Kusto」のログクエリ時のテクニック:Tech TIPS
Azureの各種サービスが出力するログを「Log Analytics」を分析するには、「Kusto」という言語でクエリを記述する必要がある。Azure App Service/Front Doorを主な対象として、筆者がよく使っているKustoのクエリ時のテクニックを紹介しよう。
対象:Azure Log Analytics、Kusto(KQL)、Azure Front Door、Azure App Service
「Azure Log Analytics」「Kusto」でのログ分析が難しい!?
Azureの各サービスが出力するログを分析する場合、「Azure Log Analytics」サービスなら簡単にセットアップできて便利だ。
ただLog Analyticsでは、「Kusto」(KQL: Kusto Query Language)と呼ばれる言語で分析時のフィルタや出力内容などを指定する必要がある。構造はSQL言語によく似ているとはいえ、望み通りの統計などを得るには、それなりにKustoの関数や演算子を使いこなす必要がある。Microsoft Learnのレファレンスなどを参照しつつ、試行錯誤を繰り返すこともよくあるのではないだろうか。
本Tech TIPSでは、そのような時に役立つ、Kustoのちょっとしたクエリのテクニックを紹介したい。ログの主な対象はAzure App ServiceとAzure Front Doorとする。
クエリ文字列をパラメータごとに分割して出力するには
Azure App ServiceでもAzure Front Doorでも、HTTPリクエストに含まれるクエリ文字列(クエリストリング)は特にパースされることなく、テーブル列の1つに格納されている。
これをクエリ文字列に含まれるパラメータごとに分割して見やすくするには、「parse_urlquery()」「parse_url()」関数を利用する。これらの関数の引数にクエリ文字列またはURLを指定して得られる戻り値に対し、「Query Parameters」というキーを指定すると、各クエリパラメータ名をキーとした値(動的なオブジェクト)が返される。
AppServiceHTTPLogs
| extend query = parse_urlquery(CsUriQuery)["Query Parameters"]
| extend tag = query["tag"], lat = query["lat"], lon = query["lon"]
AzureDiagnostics
| extend query = parse_url(requestUri_s)["Query Parameters"]
| extend tag = query["tag"], lat = query["lat"], lon = query["lon"]
上記のクエリの場合、「tag」「lat」「lon」という計算列が新たに設けられ、そこに同名のクエリパラメータの値が格納される。「project」演算子を指定しない場合、テーブル列に加えてこれらの計算列も出力(表示)される。
「summarize」でクエリパラメータの出現数の統計を取る際の注意点
前述のクエリで、パラメータごとに出現数の統計を取りたい、ということもよくあるだろう。
その場合、「summarize」演算子の引数に前述のパラメータ値を格納した計算列を指定することになる。その際、単に指定するとエラーになってしまうので、「tostring()」関数で計算列の値(動的なオブジェクト)を文字列に変換してからsummarizeの引数に指定する必要がある。
AppServiceHTTPLogs
| extend query = parse_urlquery(CsUriQuery)["Query Parameters"]
| extend tag = query["tag"], lat = query["lat"], lon = query["lon"]
| summarize RequestCount = count() by tostring(tag)
微妙に異なる複数のテーブル列名に要注意
Log Analyticsで同じ意味なのに、名前が異なる複数のテーブル列に出くわしたことはないだろうか。
筆者はAzure Front Doorで過去に、クライアントIPアドレス(HTTPリクエストなどの送信元のIPアドレス)のテーブル列として「clientIP_s」「clientIp_s」(「p」が大文字と小文字)の2種類を目撃したことがある。同様に「errorInfo_s」「ErrorInfo_s」というテーブル列もあった。これらはカテゴリによって別々のテーブル列名が割り当てられていた。
このような状況で同じクエリの記述を使い回そうとすると、カテゴリによって列名が異なるせいでエラーになる。かといって、カテゴリごとに列名を正しく書き直すのは面倒だ。
このような場合、以下のように複数の列名を「extend」で別の計算列にまとめるという手がある。「iif()」は、第1引数が真なら第2引数を、偽なら第3引数を返す関数で、「isempty()」は引数が空文字列なら真を返す関数だ。
AzureDiagnostics
| extend client_ip = iif(isempty(clientIP_s), clientIp_s, clientIP_s)
| top 5 by TimeGenerated desc
| project TimeGenerated, client_ip, UserAgent
上記の場合、テーブル列「clientIP_s」「clientIp_s」のうち、どちらか有効な方のIPアドレスが計算列「client_ip」に出力される。後は「client_ip」で参照すれば、テーブル列に左右されずにクエリを正常に実行できる。
計算列でフィルタしていてクエリが遅いと感じたら
上記のように「extend」は便利な演算子だ。ただ、生成された計算列で絞り込み(フィルタリング)をするとクエリが遅くなる場合がある(特に計算列の算出方法が複雑な場合)。例えば、「where」演算子を使って計算列が特定の条件に合致するものだけに絞り込む、といったクエリが挙げられる。
AzureDiagnostics
| extend client_ip = iif(isempty(clientIP_s), clientIp_s, clientIP_s)
| where client_ip startswith "111.98."
| top 5 by TimeGenerated desc
もし、こうしたクエリが遅いと感じたら、絞り込みの対象を計算列からテーブル列に戻してみて、クエリの時間が短くならないか確認してみよう。その際、出力されるテーブル列の名前を変えたければ、以下のように「project-rename」演算子を使うと、計算列を生成せずに済む。
AzureDiagnostics
| where clientIP_s startswith "111.98."
| top 5 by TimeGenerated desc
| project-rename client_ip = clientIP_s
Copyright© Digital Advantage Corp. All Rights Reserved.