このトピックでは、さまざまなシナリオで構造化プロセス言語(SPL)を使用する方法について説明します。
SPL 文の最適化
多くの場合、特定のデータ処理結果を得るために、SPL 文をいくつかの方法で記述できます。簡潔で効率的な SPL 文を作成すると、メンテナンスが簡素化され、パフォーマンスが向上します。次の表にいくつかの提案を示します。
提案 | 最適化前 | 最適化後 |
連続する `where` 句を 1 つにまとめます。 | | |
連続する `extend` 句を 1 つにまとめます。 | | |
`extend` と `project-away` を `project-rename` に置き換えます。 | | |
必要な場合を除き、`extend` を使用して新しいフィールドを作成する代わりに、フィールド値をその場で変更します。 | | |
特殊フィールドの処理
時間フィールド
SPL の実行中、SLS ログ時間フィールドのデータ型は常に INTEGER または BIGINT です。SLS ログフィールドには、データタイムスタンプフィールド __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 //timeをtsとmsに分割 | extend ts=date_parse(ts, '%Y-%m-%d %H:%i:%S') //tsを日付型に変換 | extend __time__=cast(to_unixtime(ts) as INTEGER) //__time__にtsのUNIXタイムスタンプを代入 | extend __time_ns_part__=cast(ms as INTEGER) * 1000000 //__time_ns_part__にmsのナノ秒部分を代入 | project-away ts, ms //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 の定義については、「データエンコーディング」をご参照ください)。生データが標準の LogGroup エンコーディングに準拠していない場合、一部の予約フィールドが標準の場所ではなく LogContent に含まれている可能性があります。SPL はこれらの競合を次のように処理します。
__source__、__topic__、__time__、__time_ns_part__フィールドの場合、SPL は標準の LogGroup エンコーディングから値を読み取り、同じ名前の LogContent フィールドを無視します。__tag__:プレフィックスが付いたタグフィールドの場合、SPL は最初に標準の LogGroup エンコーディングから値を読み取ろうとします。値が見つからない場合、SPL は LogContent から値を読み取ります。たとえば、__tag__:ipフィールドの場合、SPL は最初に LogTag リストからキーipを持つフィールドを読み取ろうとします。フィールドが存在しない場合、SPL は LogContent のカスタムログフィールドからキー__tag__:ipを持つログフィールドを読み取ります。
全文検索の __line__ フィールド
これは、SLS スキャンクエリ機能に適用されます。
[コンソール] で、または GetLogstoreLogs API 操作を使用する場合に、生ログをフィルタリングするには、`__line__` フィールドを使用できます。
例
ログでキーワード `error` を検索します。
* | where __line__ like '%error%' //__line__ が '%error%' を含むログに `__line__` という名前のフィールドがある場合は、バックティック(`` ` ``)で名前を囲んで参照します(例 :
`__line__`)。* | where `__line__` ='20' //`__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 データ処理中に、次の 2 つのシナリオで null 値が生成されます。
SPL 式で使用されるフィールドが入力データに存在しない場合、計算中にその値は null として扱われます。
SPL 式の計算中に例外が発生した場合、結果は null になります。たとえば、`cast` 型変換が失敗した場合、または配列インデックスが範囲外の場合です。
例
フィールドが存在しない場合、計算ではその値は null として扱われます。
SPL 文
* | extend withoutStatus=(status is null) //status が null の場合、withoutStatus に true を代入します。入力データ
# エントリ 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) //values を JSON として解析します。 | extend values=cast(values as ARRAY(BIGINT)) //values を 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' //user に 'Alice' を代入します。 | extend phone = 'Alice''s Phone' //phone に 'Alice's Phone' を代入します。出力結果
user: Alice phone: Alice's Phone
二重引用符
二重引用符は、フィールド名を囲むために使用されます。フィールド名に二重引用符が含まれている場合は、追加の二重引用符を使用してエスケープする必要があります。
例
SPL 文
* | extend user_name = 'Alice' //user_name に 'Alice' を代入します。 | extend "user name" = 'Alice' //"user name" に 'Alice' を代入します。 | extend "user""name" = 'Alice' //"user"name" に 'Alice' を代入します。出力結果
user_name: Alice user name: Alice user"name: Alice
その他の特殊文字
例 1
SPL では、バックスラッシュ(\)はエスケープ文字ではないため、そのまま保持されます。
SPL 文
* | extend a = 'foo\tbar' //a に 'foo\tbar' を代入します。 | extend b = 'foo\nbar' //b に 'foo\nbar' を代入します。出力結果
a: foo\tbar b: foo\nbar
例 2
文字列にタブ文字や改行などの特殊文字を含める必要がある場合は、chr 関数を使用して文字列を連結できます。
SPL 文
* | extend a = concat('foo', chr(9), 'bar') //a に 'foo' + タブ + 'bar' を代入します。 | extend b = concat('foo', chr(10), 'bar') //b に 'foo' + 改行 + 'bar' を代入します。出力結果
a: foo bar b: foo bar
エラー処理
構文エラー
構文エラーは、SPL 文の形式が正しくない場合に発生します。たとえば、命令名が正しくない場合、キーワード参照エラー、または型設定エラーです。構文エラーが発生した場合、SPL はデータを処理しません。エラーメッセージに基づいて文を変更する必要があります。
データエラー
データエラーは、SPL の実行中に関数または変換が失敗した場合に発生します。SPL は結果のフィールドを null に設定します。データエラーはどの行でも発生する可能性があるため、SPL はランダムにサンプリングし、一部のエラーメッセージのみを返します。これらのエラーを無視するか、データの内容に基づいて SPL 文を変更できます。
データエラーによって実行プロセス全体が停止することはありません。SPL 文は引き続き結果を返しますが、エラーが発生したフィールドの値は null になります。必要に応じて、これらのエラーを無視できます。
実行タイムアウト
SPL 文にはさまざまな命令が含まれており、それらの実行時間はデータのシナリオによって異なります。SPL 文の合計実行時間がデフォルトのタイムアウト期間を超えると、実行が停止し、タイムアウトエラーが返されます。この場合、実行結果は空になります。デフォルトのタイムアウト期間は、スキャンクエリ、リアルタイム消費、および Logtail コレクションで異なる場合があります。
このエラーが発生した場合は、複雑な正規表現を簡素化したり、パイプラインの数を減らしたりするなどして、SPL 文を調整して複雑さを軽減できます。
メモリ制限を超えました
SPL 文にはさまざまな命令が含まれており、それらのメモリ消費量はデータのシナリオによって異なります。SPL の実行は、特定のメモリクォータに制限されています。このクォータを超えると、実行は失敗し、「メモリ制限を超えました」エラーが返されます。この場合、実行結果は空になります。デフォルトのメモリクォータは、スキャンクエリ、リアルタイム消費、および Logtail コレクションで異なる場合があります。
このエラーが発生した場合は、SPL 文を調整して複雑さを軽減するか、パイプラインの数を減らすか、生データのサイズが大きすぎるかどうかを確認できます。