Azure Log Analytics と Kusto (KQL) 入門 2 - 主なスカラオペレーターの使い方

:::note info
本記事は2018年に最初に書いたものですが、2023年現在まで少しずつ更新しております。
:::

# 本記事について

本記事は、私が Qiita でまとめている、「Azure Log Analytics と Kusto (KQL) 入門」の第二弾となります。本稿では、第一弾の[Azure Log Analytics と Kusto (KQL) 入門 - 良く使われるオペレーターの使い方](https://qiita.com/YoshiakiOi/items/f2cb2b5626c2f7f71dba)に続いて、次は Kusto の主なスカラオペレーター (呼び出しに対して単独の値を返すようなオペレーター) の使い方を見ていきます

https://qiita.com/YoshiakiOi/items/f2cb2b5626c2f7f71dba

また、第三弾として、Join の記事を2023年7月に公開しております。こちらも併せてご覧ください。

https://qiita.com/YoshiakiOi/items/95dd309fe0dcfc20a31a

また、ログデータが溜まっていて、誰でも簡単にクエリを試し書きできるデモ環境 LADemo へは[こちら](https://aka.ms/LADemo)からアクセスしてください。本稿でも、基本的にはこのデモ環境で試せるクエリを集めています。

https://aka.ms/LADemo

前回と同じく、今回も Pluralsight の下記講座をもとに、Kusto (KQL) のオペレーターの利用法を見ていきたいと思います。以下で紹介されているオペレーターは、4. Scalar Operatorsで紹介されているオペレーターが中心になっています。

https://aka.ms/KQLPluralsight


# スカラオペレーターの使い方を実際のクエリとともに見ていく

以下で登場するサンプルクエリの結果は、実際に上記のデモ環境で見れるようになっています。特に、一部のオペレーターは実際にクエリ結果を見てみないと理解しづらいものもあるかと思いますので、ぜひ試しながらお読みください。

## 時刻に関する主なスカラオペレーター

### datetime 型について
スカラオペレーターを学ぶ前に、Kusto の datetime 型について確認しておきます。これは、時間と日にちを表現するデータ型で、Log Analyticsでは、時刻は全て UTC で扱われます。( GUI の表示画面でローカル時刻にすることができます。）

#### datetime 型の四則演算について

以下例のように、時間の四則演算ができます。

例：ログ生成時間から現在時刻までの経過時間を時間単位と日単位で表示

```
SigninLogs
| extend Time_elapsed = now() - TimeGenerated
| extend Time_elapsed_day = Time_elapsed/1d
```

＊ now() オペレーターについては、次章を参照。

### now - 現在時刻（UTC）の取得するオペレーター

文法：現在時刻を取得；引数でずれ（1日前）などを指定して、その時刻を取ることもできる。

```
now()
```

例：一日前から今までの SigninLogs テーブルを取得

```
SigninLogs
| where TimeGenerated > now(-1d) and TimeGenerated < now()
```

### ago - 前の時刻を取得するオペレーター

文法：現在時刻から、N（分、時間、日など）だけ前の時刻を取得

```
ago(N)
```

例：一日前から今までの SigninLogs テーブルを取得

```
SigninLogs
| where TimeGenerated > ago(1d)
```

### startofX / endofX - 基準の最初/最後の datetime を返すオペレーター

X = (day | week | month | year)

文法：Xを基準として、その最初/最後のdatetimeを返す。

例：

```
startofday(datetime(2018-1-2 13:00:00))

--> 2018-01-02T00:00:00:00.000

startofyear(datetime(2018-1-2 13:00:00))

--> 2018-01-01T00:00:00.000
```

### between - 2つの値の間にある対象を返すオペレーター

文法：XとYの間を指定

```
between (X .. Y)
```

例：2023/1/1の最初と2023/1/2の最後までの間で生成されたものを選択する

```
SigninLogs 
| where TimeGenerated between (startofday(datetime(2023-1-1)) .. endofday(datetime(2023-1-2)))
```

### 時刻の出力を編集するオペレーターたち

#### format_datetime/format_timespan - datetime/timespan 型のデータを指定のフォーマットで出力するオペレーター

文法：

```
format_datetime/format_timespan (<datetime/timespan 型の値>, <指定のフォーマット>)
```
フォーマットについては以下を参照。

https://docs.loganalytics.io/docs/Language-Reference/Scalar-functions/format_datetime()

例：ログ生成時間を、```yyyy/MM/dd```のフォーマットで出力する

```
SigninLogs
| extend TimeGenerated_yyyyMMdd = format_datetime(TimeGenerated, "yyyy/MM/dd")
```

例：ログ生成時間から現在時刻までの経過時間を計算し、それを```hh:mm:ss```で出力する

```
SigninLogs
| extend Time_elapsed = format_timespan((now() - TimeGenerated), "hh:mm:ss")
```

#### datetime_part - datetime　から特定の項目を取り出すオペレーター

文法：datetime から特定の項目を取り出す

```
datetime_part(<取り出す項目>, datetime)
```

項目は、Hour, Day, WeeekOfYear, Month, Year など

例：特定の datetime (TimeGenerated の値) から、Hour だけを取り出す

```
SigninLogs
| extend TimeGenerated_Hour = datetime_part("Hour", TimeGenerated)
```

## 値の抽出やパースを行うスカラオペレーター

### extract - 特定条件のものだけ抽出するオペレーター

文法：特定のフィールドから正規表現部分を抜き出し、新しいフィールドを作成する。

```
extract(<正規表現>, <オプション指定用の数字(O or 1 or 2+)>, <フィールド名>, [<型>])
```

・[オプション指定用の数字](https://learn.microsoft.com/ja-jp/azure/data-explorer/kusto/query/extractfunction#parameters)：0 - 一致全体、 1 - 正規表現の最初の括弧の中身と一致、2+ - 後続のかっこの場合は２以上で指定。

例：NSG Flow Log の PublicIPs_s のフィールド（40.70.151.196|21|0|0|0|0|0 のような形で値が格納）から、IP アドレスの情報を抽出し、extend 句を使って PublicIP という新しいフィールドを作成

```
AzureNetworkAnalytics_CL 
| where isempty(PublicIPs_s) == false
| extend PublicIP=extract("(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.(([0-9]{1,3})))", 1, PublicIPs_s)
```

### parse - 特定のフィールドから、特定個所を評価・パースし、新しいフィールドを作成するオペレーター

文法：あるフィールドについて、特定の部分を取り出し、新しいフィールドとしてパースしていく

```
parse <フィールド名> with <特定の文> <新しいフィールド名> <次の文> <新しいフィールド名2> ... 
```

これだけ見てもわかりづらいので、具体例を見た方が早いです。

例：VMComputer テーブルの _ResourceId (例：/subscriptions/ebb79bc0-aa86-44a7-8111-cabbe0c43993/resourcegroups/ch1-opsrg-sec/providers/microsoft.compute/virtualmachines/ch1-jboxvm10) からサブスクリプションID, リソースグループ名、仮想マシン名を取り出して、新しい列として作成する

```
VMComputer
| where _ResourceId has "virtualmachines"
| parse _ResourceId with "/subscriptions/" SubscriptionID_parsed "/resourcegroups/" ResourceGroupName_parsed "/providers/" * "/virtualmachines/" VMName_parsed
| project TimeGenerated, _ResourceId, SubscriptionID_parsed, ResourceGroupName_parsed, VMName_parsed
```

VMComputer テーブルは、オリジナルのフィールドとしてサブスクリプション ID やリソースグループ名を持っているので、この処理自体は実際に分析する際には不要なのですが、わかりやすい例として取り上げました。

### parse_json - key/value型のデータが入ったフィールドを、dynamics 型（JSONのように扱える型）で扱えるようにするオペレーター

文法：

```
parse_json (<フィールド名>)
```

例：LocationDetails の中に入っている値を parse_json 関数を用いて抽出し、LocationDetails_parsed という新しいフィールドを作成。さらに、City = LD.City として、フィールドを整理。

```
SigninLogs 
| extend LocationDetails_parsed = parse_json(LocationDetails) 
| project TimeGenerated, UserDisplayName, Location, City=LocationDetails_parsed.city
```

### split - 特定の記号で値を分割して配列に入れ込むオペレーター

文法：特定の記号でフィールドを分割し、N番目のものを取り出す（Nは省略可能で、省略された場合は分割したArrayを出力する）。

```
split(<対象のフィールド>, <分割に使う記号>, [N])
```

例：パフォーマンスカウンター (Perf テーブル) の CounterPath (\\AppFE0000C3W\Process(ASMHost)\% Processor Time
 のような形式で格納) を '\\' で分割し、その Array (全体) と Array[2] を取り出す。

```
Perf 
| project CounterPath, CPSplit=split(CounterPath , '\\'), CPSplit2 = split(CounterPath, '\\', 2)
```
＊ '\\' はエスケープが必要

### strcat - 文字列を結合して新しいフィールドを作成するオペレーター

文法：引数で与えられた文字列を結合する

```
strcat(<文字列1>, <文字列2> ...)

--> 文字列1文字列2 ...
```

例：パフォーマンスカウンターで、Computer - ObjectName - CounterName というフィールドを作成する

```
Perf 
| extend COC = strcat(Computer, " - ", ObjectName, " - ", CounterName)
| project TimeGenerated, Computer, ObjectName, CounterName, COC, CounterValue
```

## 条件分岐を扱うスカラオペレーター

### iif - True/False の条件分岐を扱うオペレーター

文法：

```
iif (<条件式>, <条件式=Trueの時の値>, <条件式=Falseの時の値>)
```

例：Perf テーブルで % Free Space カウンターの値が50以下の時は OK、50以上の時は might want to look at this とコメントする新しいフィールドを作成する

```
Perf 
| where CounterName == "% Free Space"
| extend FreeSpaceComment = iff(CounterValue < 50, "You might want to look at this", "You're OK!")
```

### case - 複数条件の条件分岐を扱うオペレーター

文法：

```
case(<条件式1>, <条件式1=Trueの時の値>, ... , <条件式N>, <条件式N=Trueの時の値>, <条件式=Falseの時の値>)
```

例：上記の例で、10以下、30以下、50以下でコメントをつけていく

```
Perf 
| where CounterName == "% Free Space"
| extend FreeSpaceComment = case(CounterValue < 10, "Critical", CounterValue < 30, "Danger", CounterValue < 50, "You might want to look at this", "You're OK!")
```

### isempty/isnull - Empty値、NULL値かどうかを判定するオペレーター

これらは、上記 iff と一緒に使われることが多いです。

文法：

```
isempty/isnull(<判定したい値>)
```

例：パフォーマンスカウンターで、InstanceName が Empty 値のものを、No Instance Name と表記し、それ以外の時は InstanceName を代入する新しいフィールドを作成する

```
Perf 
| extend InstName = iff(isempty(InstanceName), "No Instance Name", InstanceName)
```

## 文字列の扱いを行うスカラオペレーター

Log Analyticsには、多くの文字列を扱うための判別式があります。ここでは、has/contains と inを見てみます。

### has/contains  - 特定の文字列を含んでいるかどうかを判別する

!has/!contains だと否定形、!has_cs/contains_cs だとcase sensitive（大文字・小文字を区別）となります。[クエリパフォーマンス観点で、has のほうが contains よりも早くなる可能性があります。](https://learn.microsoft.com/ja-jp/azure/data-explorer/kusto/query/contains-operator#performance-tips)

例：パフォーマンスカウンターのうち、CounterName　が　"BYTES" を含んでいるものだけを取り出す。

```
Perf | where CounterName has/contains "BYTES"
```

### in - 特定の単語のうちどれかが含まれていれば True、含まれていなければ False となる判別式

例：パフォーマンスカウンターのうち、CounterName が Disk Transfers/sec, Disk Reads/sec, Avg. Disk/sec Write であるものを返す

```
Perf | where CounterName in ("Disk Transfers/sec", "Disk Reads/sec", "Avg. Disk/sec Write")
```

# 最後に

本記事では、前稿である[Azure Log Analytics と Kusto (KQL) 入門 - 良く使われるオペレーターの使い方](https://qiita.com/YoshiakiOi/items/f2cb2b5626c2f7f71dba)と合わせて、Log Analytics やほかの Kusto (KQL) 利用サービスでクエリを行う際に抑えておきたいオペレーターの使い方を見てきました。

また、次稿は、[Azure Log Analytics と Kusto (KQL) 入門 3 - Join の使い方](https://qiita.com/YoshiakiOi/items/95dd309fe0dcfc20a31a)となります。


本稿が、Log Analytics など Kusto (KQL) を利用して検索を行うサービスを使い始める際の一助となれば幸いです。

＊本稿は、個人の見解に基づいた内容であり、所属する会社の公式見解ではありません。また、いかなる保証を与えるものでもありません。正式な情報は、各製品の販売元にご確認ください。