本文介绍查询和分析JSON日志的常见问题。
日志样例

- request字段为订单请求信息,JSON格式。一个请求中包含一个用户的多个订单,订单中包含购买的商品和支付总价。
- response字段为订单处理结果。
- 请求成功时,response字段值为SUCCESS。
- 请求失败时,response字段值为JSON格式,包含errcode和msg信息。
您可以通过Logtail将该日志采集到日志服务中,进行查询与分析。具体操作,请参见使用JSON模式采集日志。
如何设置索引?
索引是一种存储结构,用于对日志中的一列或多列进行排序。您只有设置索引后,才能进行查询和分析操作。在为JSON日志设置索引时,可能涉及如下方面的问题。
如何选择索引类型?
- 如果您需要查询日志中的所有字段,建议创建全文索引;如果您明确仅查询部分字段,可针对目标字段建立字段索引,减少索引费用。
- 如果对字段有SQL分析需求,则必须对目标字段建立索引,并开启统计功能。
例如您要统计分析request字段和response字段,则需要创建这两个字段的字段索引,并开启统计功能。
在索引配置中,如何选择字段的数据类型?
在设置索引时,字段的数据类型分为text、long、double和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叶子节点的名称是按照全路径进行重名判定的。例如为response字段设置别名为clientIp,系统不会判定该别名与request.clientIp字段名重复。
如何查询和分析有索引的JSON字段?
查询和分析语句格式为查询语句|分析语句
。在分析语句中,您必须使用双引号("")包裹字段名称,使用单引号('')包裹字符串。您还需为目标字段加上所有的父路径,格式为Key1.Key2.Key3
。例如request.clientIp
、request.param.userId
。更多信息,请参见查询和分析JSON日志。
*
and request.param.userId: 186499 |
SELECT
distinct("request.clientIp")

何时使用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类型,json_extract_scalar函数的返回值是varchar类型。
说明 此类型是指SQL语法中的数据类型,例如varchar、bigint、boolean、JSON、array、date等,与日志服务索引中的数据类型不同。您可以通过typeof函数查看SQL分析对象的数据类型。更多信息,请参见typeof函数。
- json_extract函数可以解析JSON对象中任意一块子结构,json_extract_scalar函数只解析标量类型(字符串、布尔值或者整形值)的叶子节点,返回对应的字符串。
- 使用json_extract函数进行提取。
* | SELECT json_extract(request, '$.clientIp')
查询和分析结果如下所示。
- 使用json_extract_scalar函数进行提取。
* | SELECT json_extract_scalar(request, '$.clientIp')
查询和分析结果如下所示。
* |
SELECT
split_part(
json_extract_scalar(request, '$.clientIp'),
'.',
1
) AS segment

* |
SELECT
json_array_length((json_extract(request, '$.param.orders')))

如何设置json_path?
使用json_extract等函数从JSON日志中提取字段时,您需指定json_path,用于标明需要提取JSON对象中的哪一部分。json_path格式为$.a.b
,美元符号($)代表当前JSON对象的根节点,然后通过半角句号(.)引用到待提取的节点。
* |SELECT json_extract_scalar(request, '$["http.path"]')
。
* | select json_extract_scalar(request, '$[\"http.path\"]')
。
- 查看用户第一个订单的金额。
* | 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
查询和分析结果如下所示。
