本文介紹查詢和分析JSON日志的常見問題。
日志樣例
本文中介紹的各個案例是基于如下JSON格式的訂單處理系統日志。
request字段為訂單請求信息,JSON格式。一個請求中包含一個用戶的多個訂單,訂單中包含購買的商品和支付總價。
response字段為訂單處理結果。
請求成功時,response字段值為SUCCESS。
請求失敗時,response字段值為JSON格式,包含errcode和msg信息。
您可以通過Logtail將該日志采集到日志服務中,進行查詢與分析。具體操作,請參見使用JSON模式采集日志。
如何設置索引?
索引是一種存儲結構,用于對日志中的一列或多列進行排序。您只有設置索引后,才能進行查詢和分析操作。在為JSON日志設置索引時,可能涉及如下方面的問題。
如何選擇索引類型?
日志服務索引分為全文索引和字段索引,您可以參考如下說明,選擇索引類型。更多信息,請參見創建索引。
如果您需要查詢日志中的所有字段,建議創建全文索引;如果您明確僅查詢部分字段,可針對目標字段建立字段索引,減少索引費用。
如果對字段有SQL分析需求,則必須對目標字段建立索引,并開啟統計功能。
如果您同時配置了全文索引和字段索引,則配置了字段索引的字段,以字段索引的配置為準。
例如您要統計分析request字段和response字段,則需要創建這兩個字段的字段索引,并開啟統計功能。
在索引配置中,如何選擇字段的數據類型?
在設置索引時,字段的數據類型分為text、long、double和JSON。更多信息,請參見數據類型。
當您設置JSON字段的數據類型時,可參考如下思路。
如果字段值不是標準JSON格式,可能只是包含了JSON格式的內容,則設置為text類型;如果字段值是標準JSON格式,則設置為JSON類型。
說明針對非完全合法的JSON日志,日志服務支持解析合法部分。
將某個字段設置為JSON類型后,如果對JSON對象中的某個葉子節點有進一步的分析需求,可以為葉子節點建立索引,這樣可以加快葉子節點的查詢和分析速度,但同時也會產生額外的索引費用。
日志服務支持JSON對象中的葉子節點建立索引,但不支持包含葉子節點的子節點建立索引。
日志服務不支持值為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格式,因此設置為text類型,并開啟統計功能。
創建索引后,新采集的日志將顯示為如下格式。
如何設置別名?
JSON葉子節點的路徑較長,您可以為其設置別名。更多信息,請參見列的別名。
在設置索引時,不同字段的字段名或別名不能重復。
對于JSON類型的字段,JSON葉子節點的名稱是按照全路徑進行重名判定的。例如為response字段設置別名為clientIp,系統不會判定該別名與request.clientIp字段名重復。
如何查詢和分析有索引的JSON字段?
查詢和分析語句格式為查詢語句|分析語句
。在分析語句中,您必須使用雙引號("")包裹字段名稱,使用單引號('')包裹字符串。您還需為目標字段加上所有的父路徑,格式為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函數進行查詢與分析。
字段值不一定是JSON格式或者需要先進行一些預處理。
例如response字段,只有在請求失敗時是JSON格式,并且包含errcode字段。那么您要分析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數組中提取內容,用法類似,主要區別如下:
json_extract函數的返回值是JSON類型,json_extract_scalar函數的返回值是varchar類型。
說明此類型是指SQL語法中的數據類型,例如varchar、bigint、boolean、JSON、array、date等,與日志服務索引中的數據類型不同。您可以通過typeof函數查看SQL分析對象的數據類型。更多信息,請參見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函數提取IP地址中的第一個數字。此處不支持使用json_extract函數,因為split_part函數的入參需為varchar類型。
* |
SELECT
split_part(
json_extract_scalar(request, '$.clientIp'),
'.',
1
) AS segment
查詢和分析結果如下所示。
一般情況下,如果您需要從JSON對象中提取字段進行分析,直接使用json_extract_scalar函數即可。因為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_path,用于標明需要提取JSON對象中的哪一部分。json_path格式為$.a.b
,美元符號($)代表當前JSON對象的根節點,然后通過半角句號(.)引用到待提取的節點。
如果JSON對象的字段中存在特殊字符(例如http.path字段、http path字段、http-path字段等),則需要使用中括號[]代替半角句號(.),然后使用雙引號("")包裹字段名,例如* |SELECT json_extract_scalar(request, '$["http.path"]')
。
如果是通過SDK進行查詢和分析,則需要對雙引號("")進行轉義,例如* | select json_extract_scalar(request, '$[\"http.path\"]')
。
提取JSON數組中的某個元素時,可以用中括號[]。在中括號中,通過數字來表示下標,下標從0開始。例如:
查看用戶第一個訂單的金額。
* | SELECT json_extract_scalar(request, '$.param.orders[0].payment')
查詢和分析結果如下所示。
查看用戶第一個訂單中購買的第二件商品。
* | SELECT json_extract_scalar(request, '$.param.orders[0].commodity[1]')
查詢和分析結果如下所示。
如何分析JSON數組?
當日志中有JSON數組時,您可以結合cast函數和UNNEST子句,展開JSON數組,再進行聚合統計。
示例1
例如您要統計所有請求成功的訂單的金額,可參見如下思路。
使用查詢語句過濾出請求成功的日志,然后在分析語句中使用json_extract函數提取出orders字段的值。
* and response: SUCCESS | SELECT json_extract(request, '$.param.orders')
查詢和分析結果如下所示。
將上述提取的JSON數組(JSON類型)轉換為array類型。
* 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)
查詢和分析結果如下所示。
使用json_extract_scalar提取payment字段值,再使用cast函數將其轉換為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
統計所有成功的請求中,每一種商品被購買的數量。您可以先提取order字段,將其轉換為array(json)類型,再使用UNNEST語句將其展開,展開結果中的每一行代表一個訂單。然后使用json_extract函數提取commodity字段,將其轉換為array(json)類型,再使用UNNEST語句將其展開,展開結果中的每一行代表一個商品。最后再進行分組求和。具體思路請參見示例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
查詢和分析結果如下所示。