このトピックでは、構造化プロセス言語(SPL)をさまざまなシナリオで使用する方法について説明します。
SPL 文の最適化
SPL 文は、同じデータ処理結果を得るために複数の方法で記述できる場合があります。簡潔かつ効率的な SPL 文は、メンテナンスを容易にし、パフォーマンスを向上させます。以下の表に、いくつかの推奨事項を示します。
|
推奨事項 |
最適化前 |
最適化後 |
|
連続する where 句を 1 つの句に統合します。 |
|
|
|
連続する extend 句を 1 つの句に統合します。 |
|
|
|
extend + project-away を project-rename に置き換えます。 |
|
|
|
必要でない限り、extend を使って新しいフィールドを作成する代わりに、既存のフィールドの値をインプレースで変更します。 |
|
|
特殊フィールドの取り扱い
時刻フィールド
SPL 実行中、Simple Log Service ログの時刻フィールドのデータ型は常に INTEGER または BIGINT です。これらのフィールドには、データタイムスタンプフィールド __time__ およびデータタイムスタンプフィールドのナノ秒部分 __time_ns_part__ が含まれます。
データタイムスタンプを更新するには、extend 命令を使用し、新しい値が INTEGER または BIGINT であることを確認してください。その他の命令は時刻フィールドに対して操作できません。各命令の動作は以下のとおりです:
-
project、project-away、project-rename:これらの命令は、デフォルトで時刻フィールドを保持します。時刻フィールドの名前を変更したり上書きしたりすることはできません。
-
parse-regexp および parse-json:抽出結果に時刻フィールドが含まれる場合、それらは無視されます。
例
既存の時刻文字列から時刻フィールドの値を抽出します。
-
SPL 文
* | parse-regexp time, '([\d\-\s:]+)\.(\d+)' as ts, ms | extend ts=date_parse(ts, '%Y-%m-%d %H:%i:%S') | extend __time__=cast(to_unixtime(ts) as INTEGER) | extend __time_ns_part__=cast(ms as INTEGER) * 1000000 | project-away ts, ms -
入力データ
time: '2023-11-11 01:23:45.678' -
出力結果
__time__: 1699637025 __time_ns_part__: 678000000 time: '2023-11-11 01:23:45.678'
特殊文字を含むフィールド名
フィールド名に空白やその他の特殊文字が含まれる場合、参照時に二重引用符(")で囲む必要があります。たとえば、空白を含むフィールド名 A B を参照する場合は、SPL 文内で "A B" のように指定します。次の例をご覧ください:
* | where "A B" like '%error%'
大文字小文字を区別しないフィールド名
SLS スキャンクエリでは、SPL 命令で参照されるフィールド名は大文字小文字を区別しません。たとえば、ログに Method というフィールドが含まれている場合、SPL 命令内で method や METHOD のように参照できます。
これは Simple Log Service のスキャンクエリ機能に適用されます。詳細については、「スキャンクエリ」をご参照ください。
例
where 句で大文字小文字を区別しないフィールド名を使用します。
-
SPL 文
* | where METHOD like 'Post%' -
入力データ
Method: 'PostLogstoreLogs' -
出力結果
Method: 'PostLogstoreLogs'
フィールド名の競合の取り扱い
ログのアップロードまたは SPL 実行中に、大文字小文字を区別する処理によりフィールド名の競合が発生することがあります。たとえば、生ログに Method および method の両方のフィールドが含まれている場合などです。SPL では、シナリオに応じてこれらの競合を異なる方法で解決します。
このような状況を回避するには、生ログ内のフィールド名を標準化することを推奨します。
入力データにおける競合
生ログに、大文字小文字を無視した場合に同一となるフィールド名(たとえば Status および status)が含まれている場合、SPL はランダムに 1 つのフィールドを選択して入力に使用し、もう一方のフィールドは破棄します。たとえば:
-
SPL 文
* | extend status_cast = cast(status as bigint) -
入力データ
Status: '200' status: '404' -
処理結果
-
可能性 1:Status フィールドの値が保持されます。
Status: '200' -- 最初の列が保持され、2 番目の列は破棄されます。 status_cast: '200' -
可能性 2:status フィールドの値が保持されます。
status: '404' -- 2 番目の列が保持され、最初の列は破棄されます。 Status_cast: '404'
-
出力結果における競合
シナリオ 1:生データのフィールド競合
SPL 実行中に、大文字小文字を無視した場合に同一となるフィールド名が生成されることがあります。この場合、SPL はランダムに 1 つを選択して出力に使用します。たとえば、ログフィールドに JSON 文字列が含まれている場合、parse-json 命令を使用すると、競合するフィールド名が生成される可能性があります。たとえば:
-
SPL 文
* | parse-json content -
入力データ
content: '{"Method": "PostLogs", "method": "GetLogs", "status": "200"}' -
出力結果
-
可能性 1:Method フィールドが保持されます。
content: '{"Method": "PostLogs", "method": "GetLogs", "status": "200"}' Method: 'PostLogs' -- Method フィールドが保持されます。 status: '200' -
可能性 2:method フィールドが保持されます。
content: '{"Method": "PostLogs", "method": "GetLogs", "status": "200"}' method: 'GetLogs' -- method フィールドが保持されます。 status: '200'
-
シナリオ 2:新しく生成されたデータフィールドとの競合
曖昧さを避けるため、SPL は命令によって明示的に生成された新規フィールド名の大文字小文字を保持します。これは、extend 命令で生成されたフィールド名、および as を用いて parse-regexp や parse-csv 命令で明示的に指定されたフィールド名に適用されます。
たとえば、extend を使用して新規フィールド Method を作成した場合、結果として得られるフィールド名は Method のままになります。
-
SPL 文
* | extend Method = 'Post' -
入力データ
Status: '200' -
出力結果
Status: '200' Method: 'Post'
SLS 予約フィールドの競合の取り扱い
これは Simple Log Service のリアルタイム消費およびスキャンクエリ機能に適用されます。
予約フィールドの完全な一覧については、「予約フィールド」をご参照ください。SPL は、入力として LogGroup 構造からデータを読み込みます。LogGroup の定義の詳細については、「データエンコーディング」をご参照ください。Simple Log Service に書き込まれる生データが標準の LogGroup 形式でエンコードされていない場合、一部の予約フィールドが標準の場所ではなく LogContent に格納されることがあります。SPL はこれらの予約フィールドを以下のように処理します:
-
__source__、__topic__、__time__、および__time_ns_part__フィールドについては、SPL が標準の LogGroup 構造から値を読み取り、LogContent 内の同名フィールドは無視します。 -
__tag__:プレフィックスを持つタグフィールドについては、SPL がまず標準の LogGroup 構造から値を読み取ろうと試みます。値が見つからない場合、LogContent から読み取ります。たとえば、__tag__:ipフィールドの場合、SPL はまず LogTag リストからキーipを持つフィールドを読み取ろうと試みます。該当するフィールドが存在しない場合、SPL は LogContent のカスタムログフィールドからキー__tag__:ipを持つログフィールドを読み取ります。
全文検索のための __line__ フィールド
これは SLS スキャンクエリ機能に適用されます。
コンソールでの生ログのフィルター処理、または GetLogstoreLogs API オペレーションの使用時に、__line__ フィールドを使用できます。
例
-
ログ内にキーワード error を検索します。
* | where __line__ like '%error%' -
ログに
`__line__`という名前のフィールドが含まれている場合、バックティックで囲んで参照します。* | where `__line__` ='20'
値の保持および上書きポリシー
SPL 命令が実行される際、出力フィールドの名前が入力データ内の既存フィールドと同じ場合、そのフィールドの値を決定するポリシーは以下のとおりです:
フィールド値の保持および上書きポリシーは、extend 命令には適用されません。extend 命令では、フィールド名が競合した場合、常に新しい値が使用されます。
古い値と新しい値のデータ型が不一致の場合
入力フィールドの元の値が保持されます。
例
-
例 1:project 命令で名前を変更したフィールドが競合している場合
-
SPL 文
* | extend status=cast(status as BIGINT) -- status フィールドの型を BIGINT に変換します。 | project code=status -- 新しい値の型(BIGINT)が古い値の型(VARCHAR)と異なるため、古い値が保持されます。 -
入力データ
status: '200' code: 'Success' -
出力結果
code: 'Success'
-
-
例 2:parse-json 命令で抽出されたフィールドが競合している場合
-
SPL 文
* | extend status=cast(status as BIGINT) -- status フィールドの型を BIGINT に変換します。 | parse-json content -- status の古い型は BIGINT、新しい型は VARCHAR です。古い値が保持されます。 -
入力データ
status: '200' content: '{"status": "Success", "body": "this is test"}' -
出力結果
content: '{"status": "Success", "body": "this is test"}' status: 200 body: 'this is test'
-
古い値と新しい値のデータ型が一致する場合
入力値が null の場合、新しい値が使用されます。それ以外の場合、命令内の mode パラメーターの設定に基づいて動作が決まります。以下の表をご覧ください。
命令内で mode パラメーターが指定されていない場合、デフォルト値は overwrite です。
|
モード |
説明 |
|
overwrite |
古い値を新しい値で上書きします。 |
|
preserve |
古い値を保持し、新しい値を破棄します。 |
例
-
例 1:project 命令で名前を変更したフィールドが競合しており、型も一致しています。デフォルトモードは overwrite です。
-
SPL 文
* | project code=status -- code の古い型と新しい型はどちらも VARCHAR です。overwrite モードに基づき、新しい値が使用されます。-
入力データ
status: '200' code: 'Success' -
出力結果
code: '200'
-
-
例 2:parse-json 命令で抽出されたフィールドが競合しており、型も一致しています。デフォルトモードは overwrite です。
-
SPL 文
* | parse-json content -- status の古い型と新しい型はどちらも VARCHAR です。overwrite モードに基づき、新しい値が使用されます。 -
入力データ
status: '200' content: '{"status": "Success", "body": "this is test"}' -
出力結果
content: '{"status": "Success", "body": "this is test"}' status: 'Success' body: 'this is test'
-
-
例 3:parse-json 命令で抽出されたフィールドが競合しており、型も一致しています。モードは preserve に設定されています。
-
SPL 文
* | parse-json -mode='preserve' content -- status の古い型と新しい型はどちらも VARCHAR です。preserve モードに基づき、古い値が保持されます。 -
入力データ
status: '200' content: '{"status": "Success", "body": "this is test"}' -
出力結果
content: '{"status": "Success", "body": "this is test"}' status: '200' body: 'this is test'
-
データ型の変換
初期型
SPL がデータを処理する際、すべての入力フィールドの初期データ型は、ログの時刻フィールドを除き VARCHAR です。その後の処理ロジックで異なるデータ型が必要になる場合、データ型の変換を行う必要があります。
例
ステータスコードが 5xx のアクセスログをフィルター処理するには、比較前に status フィールドを BIGINT 型に変換する必要があります。
* -- status フィールドの初期型は VARCHAR です。
| where cast(status as BIGINT) >= 500 -- status フィールドの型を BIGINT に変換し、その後比較を行います。
型の保持
SPL のデータ処理中、extend 命令を使用してフィールドのデータ型を変換した後、その後の処理ロジックでは変換後のデータ型が使用されます。
例
* -- Logstore が入力データとして使用されます。時刻フィールドを除くすべてのフィールドは、初期状態で VARCHAR 型です。
| where __source__='127.0.0.1' -- __source__ フィールドでフィルター処理します。
| extend status=cast(status as BIGINT) -- status フィールドの型を BIGINT に変換します。
| project status, content
| where status>=500 -- status フィールドの型は BIGINT のままなので、数値 500 と直接比較できます。
SPL 式における null 値の取り扱い
null 値の生成
SPL のデータ処理中、null 値は以下の 2 つのシナリオで生成されます:
-
SPL 式で使用されるフィールドが入力データに存在しない場合、計算時にその値は null として扱われます。
-
SPL 式の計算中に例外が発生した場合、結果は null になります。たとえば、キャスト型変換が失敗した場合や、配列のインデックスが範囲外の場合などです。
例
-
フィールドが存在しない場合、計算時にその値は null として扱われます。
-
SPL 文
* | extend withoutStatus=(status is null) -
入力データ
# エントリ 1 status: '200' code: 'Success' # エントリ 2 code: 'Success' -
出力結果
# エントリ 1 status: '200' code: 'Success' withoutStatus: false # エントリ 2 code: 'Success' withoutStatus: true
-
-
計算中に例外が発生した場合、結果は null になります。
-
SPL 文
* | extend code=cast(code as BIGINT) -- code フィールドを BIGINT に変換できませんでした。 | extend values=json_parse(values) | extend values=cast(values as ARRAY(BIGINT)) | extend last=arr[10] -- 配列のインデックスが範囲外です。 -
入力データ
status: '200' code: 'Success' values: '[1,2,3]' -
出力結果
status: '200' code: null values: [1, 2, 3] last: null
-
null 値の除去
計算中の null 値を除去するには、COALESCE 式を使用できます。この式は、式のリストから最初の null でない値を返します。また、すべての式が null と評価された場合に使用するデフォルト値を設定することもできます。
例
配列の最後の要素を読み取ります。配列が空の場合、デフォルト値は 0 です。
-
SPL 文
* | extend values=json_parse(values) | extend values=cast(values as ARRAY(BIGINT)) | extend last=COALESCE(values[3], values[2], values[1], 0) -
入力データ
# エントリ 1 values: '[1, 2, 3]' # エントリ 2 values: '[]' -
出力結果
# エントリ 1 values: [1, 2, 3] last: 3 # エントリ 2 values: [] last: 0
文字のエスケープ
シングルクォート
シングルクォートは文字列リテラルを囲むために使用されます。文字列リテラルにシングルクォートが含まれる場合、それをエスケープするために追加のシングルクォートを使用する必要があります。
例
-
SPL 文
* | extend user = 'Alice' | extend phone = 'Alice''s Phone' -
出力結果
user: Alice phone: Alice's Phone
ダブルクォート
ダブルクォートはフィールド名を囲むために使用されます。フィールド名にダブルクォートが含まれる場合、それをエスケープするために追加のダブルクォートを使用する必要があります。
例
-
SPL 文
* | extend user_name = 'Alice' | extend "user name" = 'Alice' | extend "user""name" = 'Alice' -
出力結果
user_name: Alice user name: Alice user"name: Alice
その他の特殊文字
例 1
SPL では、バックスラッシュ(\)はエスケープ文字ではなく、そのまま保持されます。
-
SPL 文
* | extend a = 'foo\tbar' | extend b = 'foo\nbar' -
出力結果
a: foo\tbar b: foo\nbar
例 2
文字列にタブ文字や改行などの特殊文字を含める必要がある場合、chr 関数 を使用して文字列を連結できます。
-
SPL 文
* | extend a = concat('foo', chr(9), 'bar') | extend b = concat('foo', chr(10), 'bar') -
出力結果
a: foo bar b: foo bar
エラー処理
構文エラー
構文エラーは、SPL 文が不正な形式である場合(たとえば、誤った命令名、キーワードの参照エラー、型指定エラーなど)に発生します。構文エラーが発生した場合、SPL はデータを一切処理しません。エラーメッセージに基づいて文を修正する必要があります。
データエラー
データエラーは、SPL 実行中に関数または変換が失敗した場合に発生します。SPL は、結果として得られるフィールドを null に設定します。データエラーは任意の行で発生する可能性があるため、SPL はランダムにサンプリングして一部のエラーメッセージのみを返します。これらのエラーは無視するか、データの内容に基づいて SPL 文を修正できます。
データエラーは、実行全体を停止しません。SPL 文は引き続き結果を返しますが、エラーが発生したフィールドの値は null になります。必要に応じて、これらのエラーを無視できます。
実行タイムアウト
SPL 文にはさまざまな命令が含まれており、その実行時間はデータのシナリオによって異なります。SPL 文の合計実行時間がデフォルトのタイムアウト期間を超えると、実行が停止し、タイムアウトエラーが返されます。この場合、実行結果は空になります。デフォルトのタイムアウト期間は、スキャンクエリ、リアルタイム消費、Logtail 収集ごとに異なる場合があります。
このエラーが発生した場合、SPL 文を調整して複雑さを軽減できます。たとえば、複雑な正規表現を簡略化したり、パイプラインの数を減らしたりします。
メモリ制限超過
SPL 文にはさまざまな命令が含まれており、そのメモリ消費量はデータのシナリオによって異なります。SPL 実行は特定のメモリクォータに制限されています。このクォータを超えると、実行が失敗し、メモリ制限超過エラーが返されます。この場合、実行結果は空になります。デフォルトのメモリクォータは、スキャンクエリ、リアルタイム消費、Logtail 収集ごとに異なる場合があります。
このエラーが発生した場合、SPL 文を調整して複雑さを軽減したり、パイプラインの数を減らしたり、生データのサイズが大きすぎないか確認したりできます。