Simple Log Service (SLS) で JSON 形式のログのクエリと分析を行う際の一般的な質問と解決策 (インデックス設定、JSON 関数、JSON 配列の処理など) を説明します。
ログサンプル
このトピックの例では、注文処理システムの JSON ログを使用します。
-
request フィールドには、JSON 形式の注文リクエスト情報が含まれています。リクエストには、あるユーザーの複数の注文を含めることができ、各注文には購入商品と支払い総額が含まれます。
-
response フィールドには、注文処理結果が含まれています。
-
リクエストが成功した場合、response フィールドの値は SUCCESS になります。
-
リクエストが失敗した場合、レスポンス フィールドの値は JSON 形式で、
errcodeとmsgの情報が含まれます。
-
Logtail を使用して JSON モードでログを収集し、SLS に取り込んでクエリと分析を行えます。
インデックスの設定方法
ログデータをクエリまたは分析する前に、インデックスを設定する必要があります。JSON ログのインデックスを設定する際は、次の点を考慮してください。
インデックスタイプの選択方法
SLS は、フルテキストインデックスとフィールドインデックスをサポートしています。次の基準に基づいてインデックスタイプを選択します。詳細については、「インデックスの作成」をご参照ください。
-
すべてのフィールドをクエリするには、フルテキストインデックスを作成します。特定のフィールドのみをクエリする場合は、対象のフィールドにフィールドインデックスを作成することでコストを削減できます。
-
フィールドに対して SQL 分析を実行するには、そのフィールドのインデックスを作成し、統計を有効にする必要があります。
フルテキストインデックスとフィールドインデックスの両方が設定されている場合、フィールドインデックスが設定されているフィールドでは、フィールドインデックスが優先されます。
たとえば、request フィールドと response フィールドを分析するには、それらのフィールドにインデックスを作成して統計を有効にすることができます。
インデックス設定におけるフィールドのデータ型の選択方法
インデックスを設定する際、フィールドのデータ型を text、long、double、または JSON に設定できます。詳細については、「データ型」をご参照ください。
JSON フィールドのデータ型を設定する際は、次の点を考慮してください。
-
フィールド値が標準の JSON 形式ではないが JSON コンテンツを含んでいる場合は、データ型を
textに設定します。値が標準の JSON 形式である場合は、データ型をJSONに設定します。説明完全には有効ではない JSON ログの場合、SLS は有効な部分を解析できます。
-
データ型を
JSONに設定した後、クエリを高速化するために JSON オブジェクト内のリーフノードのインデックスを作成できます。これには追加のインデックス作成料金が発生します。 -
SLS は JSON オブジェクト内のリーフノードのインデックスをサポートしていますが、リーフノードを含む子ノードのインデックスはサポートしていません。
-
SLS は、値が JSON 配列であるフィールド、または JSON 配列内のフィールドのインデックスをサポートしていません。
ログサンプルに基づいて、次のインデックスを作成できます。
-
request フィールド
-
request: 値は JSON 形式です。データ型を JSON に設定して、統計を有効にしてください。
-
request.clientIp: このフィールドは頻繁に分析されます。個別のインデックスを作成し、データ型を text に設定して、統計を有効にします。
-
request.http.path:このフィールドはまれにしか分析されないため、個別のインデックスを作成しないでください。 分析が必要な場合は、JSON 関数を使用して直接解析できます。
-
request.param:このフィールドはリーフノードを含む子ノードであり、インデックスを作成することはできません。
-
request.param.userId: このフィールドは頻繁に分析されます。個別のインデックスを作成し、データ型を
textに設定して統計を有効にします。 -
request.param.orders:値は JSON 配列です。このフィールドにはインデックスを作成できません。
-
-
response フィールド
response フィールドの値は常に JSON 形式であるとは限らないため、データ型をテキストに設定し、統計を有効にしてください。

インデックスを作成した後、新しく収集されたログは以下の図のように表示されます。
エイリアスの設定方法
JSON リーフノードのパスが長い場合は、エイリアスを設定できます。詳細については、「列のエイリアス」をご参照ください。
-
フィールド名とエイリアスは、インデックス設定内のすべてのフィールドで一意である必要があります。
-
JSON タイプのフィールドでは、リーフノードの名前の一意性は、そのフルパスによって決定されます。たとえば、response フィールドのエイリアスを clientIp に設定した場合、システムは、このエイリアスを request.clientIp フィールド名の重複とはみなしません。
インデックス付き JSON フィールドのクエリと分析方法
クエリ・分析ステートメントは、query statement|analytic statement の形式を使用します。分析ステートメントでは、フィールド名を二重引用符 ("") で、文字列を一重引用符 ('') で囲みます。対象フィールドには、Key1.Key2.Key3 のようにフルパスを指定します。例: request.clientIp および request.param.userId。JSON ログのクエリと分析。
たとえば、ユーザー 186499 のクライアント IP アドレスを検索するには、次の文を実行します。
*
and request.param.userId: 186499 |
SELECT
distinct("request.clientIp")
クエリと分析の結果は次のとおりです。
JSON 関数の使用場面
複雑だが固定された JSON 構造を持ち、高いパフォーマンスが要求される大規模なデータセットの場合は、JSON リーフノードのフィールドインデックスを作成します。小規模なデータセットの場合は、フィールドインデックスなしで JSON 関数を使用してコストを節約できます。JSON 関数を使用すると、JSON ログを動的に処理および分析できます。場合によっては、クエリと分析に JSON 関数しか使用できないこともあります。
-
フィールド値が常に JSON 形式であるとは限らない場合や、前処理が必要な場合。
例えば、response フィールドには、リクエストが失敗した場合にのみ errcode フィールドを含む JSON が含まれます。errcode の分布を分析するには、失敗したリクエストのログをフィルタリングし、JSON 関数を使用して errcode を抽出します。
* not response:SUCCESS | SELECT json_extract_scalar(response, '$.errcode') AS errcodeクエリと分析の結果は次のとおりです。

-
インデックス作成がサポートされていない JSON ノードでは、リアルタイム分析に JSON 関数のみを使用できます。例として、request.param フィールドや request.param.orders フィールドが挙げられます。
json_extract 関数と json_extract_scalar 関数の使い分け
json_extract と json_extract_scalar はどちらも JSON オブジェクトまたは配列からコンテンツを抽出します。主な違いは次のとおりです:
-
json_extractはJSON型を返し、json_extract_scalarはvarchar型を返します。説明これは SQL のデータ型 (
varchar、bigint、boolean、JSON、array、date) を指し、SLS のインデックスデータ型とは異なります。SQL オブジェクトのデータ型を確認するには、typeof関数を使用します。詳細については、「typeof 関数」をご参照ください。 -
json_extractは JSON オブジェクトの任意の下位構造を解析できます。json_extract_scalarはスカラー型 (文字列、ブール値、または数値) のリーフノードのみを解析し、対応する文字列を返します。
request フィールドから clientIp フィールドを抽出するには、いずれかの関数を使用できます。
-
json_extractを使用してデータを抽出します。* | SELECT json_extract(request, '$.clientIp')クエリと分析の結果は次のとおりです。

-
json_extract_scalarを使用してデータを抽出します。* | SELECT json_extract_scalar(request, '$.clientIp')クエリと分析の結果は次のとおりです。

clientIp の値の最初の部分を抽出するには、まず json_extract_scalar を使用して clientIp を抽出し、次に split_part を使用して最初の部分を抽出します。split_part は varchar 型の入力を必要とするため、ここでは json_extract を使用できません。
* |
SELECT
split_part(
json_extract_scalar(request, '$.clientIp'),
'.',
1
) AS segment
クエリと分析の結果は次のとおりです。
ほとんどの場合、json_extract_scalar を使用します。戻り値の型が varchar であり、他の関数と簡単に組み合わせることができるためです。JSON 構造自体を分析する場合は、json_extract を使用します。たとえば、リクエスト内の注文数 (request.param.orders JSON 配列の長さ) をカウントする場合などです。
* |
SELECT
json_array_length((json_extract(request, '$.param.orders')))
クエリと分析の結果は次のとおりです。
json_extract_scalar は varchar 型を返します。たとえば、上記の結果の数値 2 は varchar です。合計などの計算を実行するには、まず cast を使用して bigint に変換する必要があります。詳細については、「型変換関数」をご参照ください。
json_path の設定方法
json_extract などの関数を使用する場合、JSON パス を指定して、抽出する JSON オブジェクトの部分を指示します。フォーマットは $.a.b で、$ はルートノードを表し、ピリオド (.) は子ノードを参照します。
JSON フィールド名に http.path、http path、http-path などの特殊文字が含まれている場合は、ピリオドの代わりに角括弧 [] を使用し、フィールド名を二重引用符で囲みます。例: * |SELECT json_extract_scalar(request, '$["http.path"]')。
SDK を使用してクエリを実行する場合は、ダブルクォーテーションをエスケープしてください。 例: * | select json_extract_scalar(request, '$[\"http.path\"]')。
JSON 配列から要素を抽出するには、ゼロベースのインデックスとともに角括弧 [] を使用します。例:
-
ユーザーの最初の注文の支払い額を表示します。
* | SELECT json_extract_scalar(request, '$.param.orders[0].payment')クエリと分析の結果は次のとおりです。

-
ユーザーの最初の注文で購入された 2 番目の商品を表示します。
* | SELECT json_extract_scalar(request, '$.param.orders[0].commodity[1]')クエリと分析の結果は次のとおりです。

JSON 配列の分析方法
JSON 配列を分析するには、cast と UNNEST 句を組み合わせて配列を展開し、その結果を集計します。
例 1
成功したすべての注文の合計支払い額を計算するには:
-
成功したリクエストをフィルタリングし、
json_extractを使用して orders フィールドを抽出します。* and response: SUCCESS | SELECT json_extract(request, '$.param.orders')クエリと分析の結果は次のとおりです。

-
JSON 配列を
array(json)型に変換します。* and response: SUCCESS | SELECT cast( json_extract(request, '$.param.orders') AS array(json) )クエリと分析の結果は次のとおりです。

-
UNNESTを使用して配列を展開します。* and response: SUCCESS | SELECT orderinfo FROM log, unnest( cast( json_extract(request, '$.param.orders') AS array(json) ) ) AS t(orderinfo)クエリと分析の結果は次のとおりです。

-
payment の値を
json_extract_scalarで抽出し、bigintにキャストして合計します。* and response: SUCCESS | SELECT sum( cast( json_extract_scalar(orderinfo, '$.payment') AS bigint ) ) FROM log, unnest( cast( json_extract(request, '$.param.orders') AS array(json) ) ) AS t(orderinfo)クエリと分析の結果は次のとおりです。

例 2
成功したすべてのリクエストにおける各商品の数量をカウントするには、orders フィールドを抽出し、array(json) にキャストして UNNEST で展開します。各行が 1 つの注文を表します。次に、commodity フィールドを抽出し、再度キャストして展開します。各行が 1 つの商品を表します。最後に、グループ化してカウントします。
*
and response: SUCCESS |
SELECT
item,
count(1) AS cnt
FROM (
SELECT
orderinfo
FROM log,
unnest(
cast(
json_extract(request, '$.param.orders') AS array(json)
)
) AS t(orderinfo)
),
unnest(
cast(
json_extract(orderinfo, '$.commodity') AS array(json)
)
) AS t(item)
GROUP BY
item
ORDER BY
cnt DESC
クエリと分析の結果は次のとおりです。
