DataPrime 经营者

本指南提供了 IBM® Cloud Logs DataPrime 操作符术语表。

block

filter 的否定。 过滤掉条件为真的所有事件。 使用 filter!(condition) 可以达到同样的效果。

block $d.status_code >= 200 && $d.status_code <= 299         # Leave all events which don't have a status code of 2xx

数据通过以下字段公开:

  • $m - 事件元数据

    • timestamp
    • severity- 可能的值是 Verbose, Debug, Info, Warning, ErrorCritical
    • priorityclass- 可能的值是 high, mediumlow
    • logid
  • $l - 事件标签

    • applicationname
    • subsystemname
    • category
    • classname
    • computername
    • methodname
    • threadid
    • ipaddress
  • $d -用户数据

bottom

无分组变化:将返回的行限制为指定数目,并通过一组表达式对结果排序。

order_direction := "descending"/"ascending" according to top/bottom
bottom <limit> <result_expression1> [as <alias>] [, <result_expression2> [as <alias2>], ...] by <orderby_expression> [as alias>]

例如,以下查询:

bottom 5 $m.severity as $d.log_severity by $d.duration

将产生以下形式的日志:

[
   { "log_severity": "Debug", "duration":  1000 }
   { "log_severity": "Warning", "duration": 2000 },
   ...
]

分组变化:将返回的行限制为指定的数量,并通过一组聚合表达式进行分组,以及通过一组表达式进行排序。

order_direction := "descending"/"ascending" according to top/bottom

bottom <limit> <(groupby_expression1|aggregate_function1)> [as <alias>] [, <(groupby_expression2|aggregate_function2)> [as <alias2>], ...] by <(groupby_expression1|aggregate_function1)> [as <alias>]

例如,以下查询:

bottom 10 $m.severity, count() as $d.number_of_severities by avg($d.duration) as $d.avg_duration

将产生以下形式的日志:

[
   { "severity": "Warning", "number_of_severities": 50, avg_duration: 1000 },
   { "severity": "Debug", "number_of_severities":  10, avg_duration: 2000 }
   ...
]

支持的聚合功能列于“聚合功能”部分。

choose

只保留提供的键路径,放弃所有其他键。 完全支持在输出中嵌套关键路径。

(choose|select) <keypath1> [as <new_keypath>],<keypath2> [as <new_keypath>],...

示例:

choose $d.mysuperkey.myfield
choose $d.my_superkey.mykey as $d.important_value, 10 as $d.the_value_ten

convert

转换键的数据类型。

datatypes 关键字是可选的,可用于提高可读性。

(conv|convert) [datatypes] <keypath1>:<datatype1>,<keypath2>:<datatype2>,...

示例:

convert $d.level:number
conv datatypes $d.long:number,$d.lat:number
convert $d.data.color:number,$d.item:string

count

返回包含前面运算符产生的行数的单行。

count [into <keypath>]

可以提供一个别名来覆盖将写入结果的键路径。

例如,查询的以下部分:

count into $d.num_rows

将产生以下形式的单行:

{ "num_rows": 7532 }

countby

返回按表达式分组的所有行数。

countby <expression> [as <alias>] [into <keypath>]

可以提供别名来覆盖结果将写入的键路径。

例如,查询的以下部分

countby $d.verb into $d.verb_count

结果是每个组都有一行。

其功能与

groupby $data.verb calculate count() as $d.verb_count

create

创建一个新键,并将其值设置为表达式的结果。 键的创建是细粒度的,这意味着路径中的父键不会被覆盖。

  (a|add|c|create) <keypath> from <expression> [on keypath exists (fail|skip|overwrite)] [on keypath missing (fail|create|skip)] [on datatype change (skip|fail|overwrite)

可以通过添加以下条款来控制创建:

  • 通过添加 keypath exists,可以选择在关键路径已经存在的情况下如何操作。

    • overwrite- 覆盖旧值。 这是默认值

    • fail- 查询失败

    • skip- 跳过创建密钥

  • 添加 keypath missing 可选择在新的关键路径不存在时如何处理。

    • create- 创建密钥。 这是默认值

    • fail- 查询失败

    • skip- 跳过创建新密钥

  • 如果键已经存在,而新数据改变了值的数据类型,则在 datatype changed 上进行添加,选择如何处理。

    • overwrite- 重写数值。 这是缺省值。

    • fail- 查询失败

    • skip- 保留键的原始值(和类型)

示例:

create $d.radius from 100+23
c $d.log_data.truncated_message from $d.message.substring(1,50)
c $data.trimmed_name from $data.username.trim()

create $d.temperature from 100*23 on datatype changed skip

distinct

为所提供表达式的每个不同组合返回一行。

distinct <expression> [as <alias>] [, <expression_2> [as <alias_2>], ...]

该运算符的功能与 groupby 相同,但没有任何聚合函数。

enrich

利用查找表中的附加上下文丰富日志内容。

使用数据流 数据流图标 > Data Enrichment > Custom Enrichment 上传查找表。

enrich <value_to_lookup> into <enriched_key> using <lookup_table>
  • value_to_lookup- 将在查找表中查找的字符串表达式。

  • enriched_key- 用于存储浓缩结果的目标密钥。

  • lookup_table- 要使用的自定义充实表的名称。

表格的列将作为子键添加到目标键中。 如果 value_to_lookup 未找到,目标键将为空。 然后,您可以使用 DataPrime 功能过滤结果,如按丰富字段中的特定值过滤日志。

示例:

原始日志:

{
    "userid": "111",
    ...
}

自定义丰富查找表名为 my_users

示例查找表
标识 姓名 部门
111 John 金融
222 艾米莉

运行以下查询

enrich $d.userid into $d.user_enriched using my_users

将产生以下丰富的日志:

{
    "userid": "111",
    "user_enriched": {
        "ID": "111",
        "Name": "John",
        "Department": "Finance"
    },
    ...
}

在使用 enrich 时,请考虑以下几点:

  • 运行 DataPrime 查询源 lookup_table 查看浓缩表。

  • 如果原始日志已包含增强密钥:

    • 如果 value_to_lookup 存在于 lookup_table 中,子键将用新值更新。 如果 value_to_lookup 不存在,它们的当前值将保持不变。

    • lookup_table 中不属于列的任何其他子键将保留其现有值。

  • lookup_table 中的所有值都被视为字符串。 这意味着:

    • value_to_lookup 必须是字符串格式。

    • 所有值都以字符串格式丰富。 然后,您可以使用适当的函数将它们转换成您喜欢的格式(例如 JSON、时间戳)。

extract

从某个字符串值中提取数据到一个新对象中。 支持多种提取方法。

(e|extract) <expression> into <keypath> using <extraction-type>(<extraction-params>) [datatypes keypath:datatype,keypath:datatype,...]

以下是支持的提取方法及其参数:

  • regexp- 根据 regexp 捕捉组创建新对象

  • e- 名称为 capture-groups 的正则表达式。

示例:

extract $d.my_text into $d.my_data using regexp(e=/user (?<user>.*) has logged in/)
  • kv- 从包含 key=value key=value......pairs 的字符串中提取新对象

  • pair_delimiter- 在数据对之间的分隔符。 默认为(空格)

  • key_delimiter- 键和值之间的分隔符。 默认为 =。

示例:

extract $d.text into $d.my_kvs using kv()
e $d.text into $d.my_kvs using kv(pair_delimiter=' ',key_delimiter='=')
  • jsonobject- 从包含编码 json 对象的字符串中提取一个新对象,可能会尝试在解码为 json 之前先解码字符串

  • max_unescape_count- 解析 json 之前要取消转义的最大级数。 缺省值为 1。 当设置为 1 或更多时,引擎将检测值是否包含转义 JSON 字符串,并对其进行解cape,直到超过其可解析次数或最大解cape次数。

示例:

e $d.json_message_as_str into $d.json_message using jsonobject(max_unescape_count=1)

通过使用数据类型子句,可以在提取过程中提供数据类型信息。 例如,将数据类型 my_field:number 添加到提取中,会导致提取 my_field 关键路径变成数字,而不是字符串。 例如:

extract $d.my_msg into $d.data using kv() datatypes my_field:number

提取的数据总是作为一个对象进入一个新的键路径,以便在这个新对象中对新键进行进一步处理。 例如:

# Assuming a dataset which look like that:
{ "msg": "query_type=fetch query_id=100 query_results_duration_ms=232" }
{ "msg": "query_type=fetch query_id=200 query_results_duration_ms=1001" }

# And the following DataPrime query:
source logs
  | extract $d.msg into $d.query_data using kv() datatypes
query_results_duration_ms:number
  | filter $d.query_data.query_results_duration_ms > 500

# The results will contain only the second message, in which the duration is greater than 500 ms

filter

过滤事件,只留下条件求值为 "true "的事件。

(f|filter|where) <condition-expression>

示例:

f $d.radius > 10
filter $m.severity.toUpperCase() == 'INFO'
filter $l.applicationname == 'myapp'
filter $l.applicationname == 'myapp' && $d.msg.contains('failure')

与 null 的比较仅适用于标量值,在 JSON 子树中总是返回 null。

当使用条件将关键路径与空值进行比较时,这只能对标量值(字符串、数字、时间戳等)有效。 对于给定文档中的 JSON 对象,与 null 比较总是返回 null。

使用带有功能的过滤器来执行复杂的搜索。

示例:

filter in($l.applicationname, 'ibm-audit-event', 'ibm-platform-logs') #
filter ipInSubnet(ip_address, '155.64.5.20/24')

e - 过滤指定范围内的 IP 地址。 过滤器可与函数结合使用,以很少的语法执行复杂的搜索,例如使用 ipInSubnet 函数:

过滤器 ipInSubnet(ip_address, ' 154.67.8.20/24 ')

groupby

根据指定的分组表达式对前面运算符的结果进行分组,并为创建的每个分组计算集合函数。

groupby <grouping_expression> [as <alias>] [, <grouping_expression_2> [as <alias_2>], ...] [calculate]
  <aggregate_function> [as <result_keypath>]
  [, <aggregate_function_2> [as <result_keypath_2], ...]

例如,以下查询:

groupby $m.severity calculate sum($d.duration)

将产生以下形式的日志:

{ "severity": "Warning", "_sum": 17045 }

分组表达式的关键路径将始终位于 $d 下。 使用 as 关键字,我们可以重命名分组表达式和聚合函数的关键路径。 例如:

groupby $l.applicationname as $d.app calculate sum($d.duration) as $d.sum_duration

将产生以下形式的日志:

{ "app": "web-api", "sum_duration": 17045 }

使用 groupby 操作符查询时,可以对结果桶应用 聚合函数 (如 avg, max, sum )。 该功能可让您在表达式内部操作聚合表达式,从而同时计算和操作数据。

join

Join 根据指定条件将当前(左侧)查询结果与第二个(右侧)查询结果合并。 它提供多种形式来控制数据的组合方式,并支持嵌套,允许正确的查询包含自己的 join 命令。

Join 支持三种变体:

join left|join
对于左侧查询中的每个事件,该命令都会根据指定条件从右侧查询中选择一个匹配事件。 如果未找到匹配项,则会包含左侧查询中所有事件的记录。 右侧查询中不匹配的记录将设置为 null
join full
返回每个事件的一行,包括在任一查询(左侧或右侧)中可能没有匹配的事件,用 null 填充缺失值。
join inner
只返回两次查询结果均为非空的记录。
join cross
将左侧查询中的每一行与右侧查询中的每一行配对,生成完整的笛卡尔乘积。 与其他 join 类型不同,join cross 不支持 onusing conditions。 它的功能类似于 join inner,但不进行任何过滤,而是返回所有可能的行组合。

对于 left (默认)、innerfull,可以使用 on 关键字指定 join 条件,或使用 keyword 指定关键路径。 笛卡尔乘积经过过滤,只保留条件为真或关键路径值两边都匹配的行。

由于所有连接(无论修饰符是什么)都基于笛卡尔乘积,因此如果 join 条件多次匹配,就会出现重复结果。 为防止意外重复,可考虑对子查询进行预处理,如使用 distinct。

语法:

<left_side_query> | join [left/inner/full] (<right_side_query>) on <condition> into <right_side_target>
<left_side_query> | join [left/inner/full] (<right_side_query>) using <join_keypath_1> [, <join_keypath_2>, ...] into <right_side_target>
<left_side_query> | join cross (<right_side_query>) into <right_side_target>

其中:

  • <right_side_query>- <right_side_query> 表示要连接的新查询。

  • <left_side_query> - <left_side_query> 表示初始查询,例如,在查询 source logs | filter x != null | join ... 中,左侧查询为 source logs | filter x != null

  • <condition>- 两个查询的结果是否应合并的条件。

    在条件中,可以使用 left=>right=> 前缀分别指左侧和右侧查询的事件。 但是,如果关键路径只存在于其中一个查询中,则不需要这样做。

    在条件中使用 == (相等)运算符时,必须将左侧查询中的键路径与右侧查询中的键路径进行比较。 不过,由于关键路径必须是唯一的,或者以 left=>right=> 为前缀,因此操作数的排序并不重要。

  • <join_keypath_n>- <join_keypath_n> 作为连接键是指连接左侧查询和右侧查询结果中给定键路径相同的结果。

  • <right_side_target>- 关键路径,连接数据将被添加到当前查询中。

join 示例

您有一个名为 users自定义丰富 表,提供与名称相关的 ID 信息:

{ "id": "111", "name": "John" }
{ "id": "222", "name": "Emily" }
{ "id": "333", "name": "Alice" }

这些数据提供登录事件和用户 ID,但不提供与用户 ID 相关的用户名。

{ "userid": "111", "timestamp": "2022-01-01T12:00:00Z" }
{ "userid": "111", "timestamp": "2022-01-01T12:30:00Z" }
{ "userid": "222", "timestamp": "2022-01-01T13:00:00Z" }
{ "userid": "222", "timestamp": "2022-01-01T13:00:00Z" }
{ "userid": "222", "timestamp": "2022-01-01T13:00:00Z" }

使用 join,您可以使用查询来返回包括所需数据在内的数据。

source users | join (source logs | countby userid) on id == userid into logins

该查询的处理过程如下:

  • source 是自定义浓缩表 (users)。

  • join 中,按 userid 字段生成计数。 这样,我们就可以得到 count 的统计数据。

  • 自定义充实表中的 id 字段与日志中的 userid 字段进行比较。

  • 结果被推入 logins 键。 如果 logins 密钥已存在于左侧查询中,则会被覆盖。

例如:

{ "id": "111", "name": "John", "logins": { "userid": "111", "_count": 2 } }
{ "id": "222", "name": "Emily", "logins": { "userid": "222", "_count": 3 } }
{ "id": "333", "name": "Alice", "logins": null }

右侧查询的结果现在位于 logins 字段内。 请注意,用户 ID 333 (Alice)没有登录,因此登录字段为 null,因为 join 条件没有匹配的结果。

join 使用 using 关键字的示例

如果我们的登录数据集是

{ "id": "111", "timestamp": "2022-01-01T12:00:00Z" }
{ "id": "111", "timestamp": "2022-01-01T12:30:00Z" }
{ "id": "222", "timestamp": "2022-01-01T13:00:00Z" }
{ "id": "222", "timestamp": "2022-01-01T13:00:00Z" }
{ "id": "222", "timestamp": "2022-01-01T13:00:00Z" }

在这种情况下,连接两边的数据都包括字段 id。 在这种情况下,可以使用 using 关键字来利用通用数据:

source users | join (source logins | countby id) using id into logins

结果类似,但返回的字段不是 userid,而是 id

{ "id": "111", "name": "John", "logins": { "id": "111", "_count": 2 } }
{ "id": "222", "name": "Emily", "logins": { "id": "222", "_count": 3 } }
{ "id": "333", "name": "Alice", "logins": null }

如果有两个字段的名称不同,但为了简化 join 查询,可以使用 move 来移动其中一个字段,使两边的键路径一致。

join 使用 left=>right=> 关键字的示例

您可以使用 left=>right=> 前缀来指代左右查询的事件。 但是,如果关键路径只存在于其中一个查询中,则不需要这样做。

利用上一个示例中的数据,考虑查询:

source users | join (source logins | countby id) on left=>id == right=>id into logins

之所以需要这样做,是因为两个数据集都包含一个同名字段 (id)。DataPrime 要唯一标识一个字段,就必须知道我们引用的是哪一方的查询。 该查询的输出结果与使用 using 关键字的查询结果相同。

在条件中使用 == (相等)运算符时,必须将左侧查询中的键路径与右侧查询中的键路径进行比较。 不过,由于关键路径必须是唯一的,或者以 left=>right=> 为前缀,因此操作数的排序并不重要。

join full 示例

您有一个名为 users自定义丰富 表,提供与名称相关的 ID 信息:

{ "id": "111", "name": "John" }
{ "id": "222", "name": "Emily" }
{ "id": "333", "name": "Alice" }

再看看这个数据集:

{ "id": "001", "timestamp": "2022-01-01T12:00:00Z" }
{ "id": "111", "timestamp": "2022-01-01T12:00:00Z" }
{ "id": "111", "timestamp": "2022-01-01T12:30:00Z" }
{ "id": "222", "timestamp": "2022-01-01T13:00:00Z" }
{ "id": "222", "timestamp": "2022-01-01T13:00:00Z" }
{ "id": "222", "timestamp": "2022-01-01T13:00:00Z" }

第二组文件(右侧查询)包括一个日志条目,其 "id": "001" 在第一组文件(左侧查询)中并不存在。 如果使用标准连接,来自右侧查询的条目将被忽略,不会出现在结果中。 要确保输出中包含 id 的每个字段,无论其是否出现在左侧或右侧查询中,都可以使用 join full

source users | join full (source logins | countby id) using id into logins

查询结果如下

{ "id": "001", "name": "null", "logins": { "id": "001", "_count": 1 } }
{ "id": "111", "name": "John", "logins": { "id": "111", "_count": 2 } }
{ "id": "222", "name": "Emily", "logins": { "id": "222", "_count": 3 } }
{ "id": "333", "name": "Alice", "logins": null }

通过使用 join full,两个数据集中的所有 id 字段都会保留,任何缺失值都会设置为 null

join full 在两个查询结果都包含时间桶时特别有用。 例如,如果左侧查询结果中缺少一个特定小时的时间桶(如 XX:XX:XX ),那么 join full 就会包含这个数据点。 这对比较图表上的两个时间序列特别有用。

join inner 示例

如果要删除左侧或右侧查询中产生空值的列结果中的任何行,请使用 join inner

利用之前的数据,该查询会删除两边数据不匹配的行:

source users | join inner (source logins | countby id) using id into logins
  • 左侧查询 source users 检索包含字段 idname 的用户数据集。

  • 右侧查询 (source logins | countby id) 检索登录数据集,按 id 进行分组,并计算每个 id 的出现次数。

  • join inner 匹配两个数据集中都存在 id 的行,并将数据合并为一条记录。

  • 最终结果不包括在两个数据集中都不匹配的行。

在这种情况下,上述两个文件集的结果如下:

{ "id": "111", "name": "John", "logins": { "id": "111", "_count": 2 } }
{ "id": "222", "name": "Emily", "logins": { "id": "222", "_count": 3 } }

join cross 示例

join cross 将左侧查询中的每一行与右侧查询中的每一行合并,生成两个集合的笛卡尔积。

假设我们从名为 users 的自定义充实表中获取了以下文件。

{ "id": "111", "name": "John" }
{ "id": "222", "name": "Emily" }
{ "id": "333", "name": "Alice" }

现在,考虑这组名为 logs 的文件。

{ "id": "111", "timestamp": "2022-01-01T12:00:00Z" }
{ "id": "111", "timestamp": "2022-01-01T12:30:00Z" }
{ "id": "222", "timestamp": "2022-01-01T13:00:00Z" }
{ "id": "222", "timestamp": "2022-01-01T13:00:00Z" }
{ "id": "222", "timestamp": "2022-01-01T13:00:00Z" }

下面的查询将生成 userslogs 数据集的笛卡尔乘积,因为 join cross 会将左侧查询中的每一条记录与右侧查询中的每一条记录配对,而不考虑任何匹配条件。

source users | join cross (source logs) into logins
{ "id": "111", "name": "John", "logins": { "id": "111", "timestamp": "2022-01-01T12:00:00Z" } }
{ "id": "111", "name": "John", "logins": { "id": "111", "timestamp": "2022-01-01T12:30:00Z" } }
{ "id": "111", "name": "John", "logins": { "id": "222", "timestamp": "2022-01-01T13:00:00Z" } }
{ "id": "111", "name": "John", "logins": { "id": "222", "timestamp": "2022-01-01T13:00:00Z" } }
{ "id": "111", "name": "John", "logins": { "id": "222", "timestamp": "2022-01-01T13:00:00Z" } }
{ "id": "222", "name": "Emily", "logins": { "id": "111", "timestamp": "2022-01-01T12:00:00Z" } }
{ "id": "222", "name": "Emily", "logins": { "id": "111", "timestamp": "2022-01-01T12:30:00Z" } }
{ "id": "222", "name": "Emily", "logins": { "id": "222", "timestamp": "2022-01-01T13:00:00Z" } }
{ "id": "222", "name": "Emily", "logins": { "id": "222", "timestamp": "2022-01-01T13:00:00Z" } }
{ "id": "222", "name": "Emily", "logins": { "id": "222", "timestamp": "2022-01-01T13:00:00Z" } }
{ "id": "333", "name": "Alice", "logins": { "id": "111", "timestamp": "2022-01-01T12:00:00Z" } }
{ "id": "333", "name": "Alice", "logins": { "id": "111", "timestamp": "2022-01-01T12:30:00Z" } }
{ "id": "333", "name": "Alice", "logins": { "id": "222", "timestamp": "2022-01-01T13:00:00Z" } }
{ "id": "333", "name": "Alice", "logins": { "id": "222", "timestamp": "2022-01-01T13:00:00Z" } }
{ "id": "333", "name": "Alice", "logins": { "id": "222", "timestamp": "2022-01-01T13:00:00Z" } }

查询结果是每个用户与每个日志条目配对:users 中的 3 条记录乘以 logs 中的 5 条记录,得到 15 条记录。 每个用户 (John, Emily, Alice) 都与每个日志条目配对。

当您想看到数据的全貌,然后在这些结果中添加 leftright join 时,使用 join cross 尤其有用。

局限性和考虑因素

在查询中包含 join 时有一些限制和注意事项:

  • join 条件仅支持键路径相等 (==)。如果需要多个相等条件,可以用 && (逻辑和)将它们组合起来。

  • 连接(当前查询或连接查询)的一边必须很小(< 200MB )。 您可以使用 filterremove 来减小查询的大小。

  • 左外连接要求条件中的所有列都非空。 任何空列都不会被连接。 要包含右侧查询空列,请使用 join full。 要排除左连接和右连接产生的所有空列,请使用 join inner

limit

将输出限制为首个 event-count 事件。

limit <event-count>

示例:

limit 100

move

将一个键(包括其子键(如果有))移动到一个新位置。

(m|move) <source-keypath> to <target-keypath>

示例:

move $d.my_data.hostname to $d.my_new_data.host
m $d.kubernetes.labels to $d.my_labels

multigroupby

multigroupby 将两个或更多查询结果串联起来,将 groupby 并入一个数据集。

使用 multigroupby

  • 效率高:多次查询只需扫描一次数据。

  • 同步:结果保持一致,避免运行单独查询时可能出现的差异。

multigroupby  (<grouping_expression_1> as <alias> [, <grouping_expression_2> as <alias_2>, ...])  [, (<grouping_expression_1> as <alias> [, <grouping_expression_2> as <alias_2>, ...]), ...][calculate]  <aggregation_expression> [as <result_keypath>] [, <aggregation_expression_2> [as <result_keypath_2], ...]

通过在两个分组中使用相同的别名 app,相同的语义将作为一个统一的字段呈现出来。 在下一个示例中,使用了不同的别名,数据是合并的,但不是合并。

示例 - Multigroupby 具有相同的别名

在本例中,我们要对日志进行如下分组:

  • 首先按 applicationname (app) 分类,然后进一步按 subsystemname (ss) 分类,提供每种组合的详细计数。

  • 然后由 applicationname 独立计算,得出每个应用程序的日志总计数,与 subsystems 无关。

source logs
| multigroupby ($l.applicationname as app, $l.subsystemname as ss),($l.applicationname as app) calculate count() | orderby app,ss

结果将类似于

[
    {
        "_count0": 241,
        "app": "monitoring24",
        "ss": "NO_SUBSYSTEM_NAME"
    },
    {
        "_count0": 231,
        "app": "monitoring24",
        "ss": "logs-opentelemetry-agent"
    },
    {
        "_count0": 15,
        "app": "monitoring24",
        "ss": "logs-opentelemetry-collector"
    },
    {
        "_count0": 487,
        "app": "monitoring24",
        "ss": null
    }
]

前三行表示 appss 每种独特组合的计数。 例如,有 241 份日志,其中应用程序 (app) 是 monitoring24,子系统 (ss) 是 NO_SUBSYSTEM_NAME。 同样,同一应用程序也有 231 份日志,但子系统 logs-opentelemetry-agent,以此类推。

最后一行是应用程序 monitoring24 的日志总数,汇总了所有子系统。 在这里,_count0 是 487,即上述所有详细计数的总和。 ss 字段为 null,表示这是整个申请的总数。

通过在两个分组中使用相同的别名 app,相同的语义将作为一个统一的字段呈现出来。 在下一个示例中,使用了不同的别名,数据是合并的,但不是合并。

示例 - 使用不同别名的 Multigroupby

现在,请考虑为查询引入 2 个不同别名的效果。 在这种情况下,第一个分组标记为 app1,用于 applicationnamess 的组合,而第二个分组标记为 app2,用于 applicationname alone

source logs | multigroupby ($l.applicationname as app1, $l.subsystemname as ss),($l.applicationname as app2) calculate count()

结果将类似于

[
    {
        "_count0": 241,
        "app1": "monitoring24",
        "app2": null,
        "ss": "logs-opentelemetry-agent"
    },
    {
        "_count0": 231,
        "app1": "monitoring24",
        "app2": null,
        "ss": "logs-opentelemetry-collector"
    },
    {
        "_count0": 15,
        "app1": "monitoring24",
        "app2": null,
        "ss": "no_subsystem_name"
    },
    {
        "_count0": 487,
        "app1": null,
        "app2": "monitoring24",
        "ss": null
    }
]

通过引入单独的别名(app1app2 ),查询保持了两个分组之间的区别,而不是合并数据。 app1 已填充和 app2null 的行对应于 applicationnamesubsystemname 的详细分组。 例如,有 241 份日志与 app1 = "monitoring24"ss = "logs-opentelemetry-agent" 相关联。 这遵循了第一种分组逻辑。

app2 已填充,app1null 的行反映了第二组的总计数,其中日志仅按 applicationname 进行汇总。 app2 = "monitoring24" 的计数是 487,而 app1ss 都是 null,以表示这种更高层次的汇总。

通过使用单独的别名(app1app2 ),查询不会合并数据,而是明确显示每个结果属于哪一组。 总体逻辑保持不变:对特定组合进行详细统计,对总数进行汇总统计。

Multigroupby 限制

multigroupby 不会返回重复组集的重复行。

如果使用 app 和 ss 运行 multigroupby,预期结果(如果允许重复)可能如下:

[
  {"app": "monitoring24", "ss": "logs-opentelemetry-agent", "_count0": 2},
  {"app": "monitoring24", "ss": "logs-opentelemetry-collector", "_count0": 2}
]

由于存在限制,multigroupby 将合并这些重复数据,并对每个唯一组合只返回一条记录,即使该组合在数据中出现多次:

[
  {"app": "monitoring24", "ss": "logs-opentelemetry-agent", "_count0": 2}
]

orderby / sortby / order by / sort by

按表达式值的升序/降序对数据进行排序。 支持按多个表达式排序。

(orderby|sortby|order by|sort by) <expression> [(asc|desc)] , ...

示例:

orderby $d.myfield.myfield
orderby $d.myfield.myfield:number desc
sortby $d.myfield desc

数值的排序可以通过将表达式转换为类型:来完成,例如 <expression>: number。 在某些情况下,这将由引擎自动推断。

redact

从某个 keypath 值替换所有与 regexp 模式匹配的子串,从而有效隐藏原始内容。

匹配关键字是可选的,可用于提高可读性。

redact <keypath> [matching] /<regular-expression>/ to '<redacted_str>'
redact <keypath> [matching] <string> to '<redacted_str>'

示例:

redact $d.mykey /[0-9]+/ to 'SOME_INTEGER'
redact $d.mysuperkey.user_id 'root' to 'UNKNOWN_USER'
redact $d.mysuperkey.user_id matching 'root' to 'UNKNOWN_USER'

remove

从对象中删除关键路径。

r|remove <keypath1> [ "," <keypath2> ]...

示例:

r $d.mydata.unneeded_key
remove $d.mysuperkey.service_name, $d.mysuperkey.unneeded_key

replace

用一个新值替换某个键的值。

如果替换值改变了键路径的数据类型,则可使用以下选项:

  • skip- 替换将被忽略

  • fail- 查询将失败

  • overwrite- 新值将覆盖之前的值,改变键路径的数据类型

replace <keypath> with <expression> [on datatype changed skip/fail/overwrite]

示例:

replace $d.message with null
replace $d.some_superkey.log_length_plus_10 with $d.original_log.length()+10 on datatype changed overwrite

roundtime

将事件发生的时间取整到某个时间间隔内,并可能为结果创建一个新键。

  • 如果未提供 source-timestamp,则使用 $m.timestamp 作为源时间戳。

  • 如果提供 source-timestamp,则其类型应为 timestamp (或转换为 )。

默认情况下,舍入结果会写回源键路径 source-timestamp。 如果输入 target-keypath,则不修改 source-timestamp,而是将结果写入新的 target-keypath

支持的时间间隔为

  • Xns - X 纳秒(注意源时间戳的分辨率)
  • Xms - X 毫秒
  • Xs - X 秒
  • Xm - X 分钟
  • Xh - X 小时
  • Xd - X 天

以及从大时间单位到小时间单位的任何组合,例如 1h30m15s

roundtime [source-timestamp] to <time-interval> [into <target-keypath>]

示例:

roundtime to 1h into $d.tm
roundtime $d.timestamp to 1h
roundtime $d.my_timestamp: timestamp to 60m
roundtime to 60s into $d.rounded_ts_to_the_minute

source

设置 DataPrime 查询所基于的数据源。

(source|from) <data_store>

data_store 可以是其中之一:

  • logs

  • 自定义增益的名称。 在这种情况下,命令将显示自定义浓缩表。

示例:

source logs

stitch

stitch 命令执行两个数据集的横向联合,将它们并排组合在一起。 它将一个数据集的行与另一个数据集的行对齐,并连接它们的列,创建一个统一的数据集。

使用 stitch 命令时:

  • 数据集必须是有序的,因为行是按顺序排列的(即数据集 A 的第 1 行与数据集 B 的第 1 行拼接)。

  • 如果一个数据集的行数多于另一个数据集,则未匹配的行将在拼接列中显示空值。

  • 生成的数据集将包含两个数据集中的所有列。

stitchunion 不同。stitch 通过逐行添加列,水平合并数据集。union 垂直添加行,将数据集堆叠在一起。

... | stitch (<subquery>) into <target-keypath>

示例:

您有这些 自定义丰富 表:

sales 数据集:

{ "product": "Widget", "sales": 100 }
{ "product": "Gadget", "sales": 200 }
{ "product": "Dashboard", "sales": 150 }

revenue 数据集:

{ "product": "Widget", "revenue": 5000 }
{ "product": "Gadget", "revenue": 8000 }
{ "product": "Dashboard", "revenue": 6000 }

在此查询中,您将并排组合这些数据集,确保其中一个数据集的每一行都与另一个数据集的相应行对齐:

source sales | orderby product
| stitch (source revenue | orderby product) into combined_data
  • source salessales 数据集中获取所有数据行,该数据集包含产品及其相应的销售数字。

  • orderby productproduct 字段对 sales 数据集排序,以创建一致的行对齐顺序。

  • stitch (source revenue | orderby product)revenue 数据集中获取数据行,并按 product 字段排序。 salesrevenue 数据集以水平方式合并,排序后根据行的顺序对齐。

  • into combined_data 将合并后的数据集存储到一个名为 combined_data 的变量中。

查询的结果是

{ "product": "Widget", "sales": 100, "combined_data": { "product": "Widget", "revenue": 5000 } }
{ "product": "Gadget", "sales": 200, "combined_data": { "product": "Gadget", "revenue": 8000 } }
{ "product": "Dashboard", "sales": 150, "combined_data": { "product": "Dashboard", "revenue": 6000 } }

如果数据集的行数不相等,stitch 命令会用 null 填补缺失值。

例如,请考虑以下数据集:

sales 数据集(3 行):

{ "product": "Widget", "sales": 100 }
{ "product": "Gadget", "sales": 200 }
{ "product": "Dashboard", "sales": 150 }

revenue 数据集(2 行):

{ "product": "Widget", "revenue": 5000 }
{ "product": "Gadget", "revenue": 8000 }

运行此查询:

source sales | orderby product
| stitch (source revenue | orderby product) into combined_data

结果为:

{ "product": "Widget", "sales": 100, "combined_data": { "product": "Widget", "revenue": 5000 } }
{ "product": "Gadget", "sales": 200, "combined_data": { "product": "Gadget", "revenue": 8000 } }
{ "product": "Dashboard", "sales": 150, "combined_data": { "product": "Dashboard", "revenue": null } }

stitch 使用说明

  • 各行必须在逻辑上相互关联,缝合才能产生有意义的结果。 确保两个数据集中的行代表相同的实体,且顺序相同。 例如,如果 sales 数据集中的 product 字段与 revenue 数据集中相应行的 product 字段不匹配,缝合将无法按预期进行。

  • 如果数据集的行数不同,结果将包括较短数据集中缺失数据的 null 值。

top

无分组变化:将返回的行限制为指定数目,并通过一组表达式对结果排序。

order_direction := "descending"/"ascending" according to top/bottom

top <limit> <result_expression1> [as <alias>] [, <result_expression2> [as <alias2>], ...] by <orderby_expression> [as alias>]

例如,以下查询:

top 5 $m.severity as $d.log_severity by $d.duration

将产生以下形式的日志:

[
   { "log_severity": "Warning", "duration": 2000 },
   { "log_severity": "Debug", "duration":  1000 }
   ...
]

分组变化:将返回的行限制为指定的数量,并通过一组聚合表达式进行分组,以及通过一组表达式进行排序。

order_direction := "descending"/"ascending" according to top/bottom

top <limit> <(groupby_expression1|aggregate_function1)> [as <alias>] [, <(groupby_expression2|aggregate_function2)> [as <alias2>], ...] by <(groupby_expression1|aggregate_function1)> [as <alias>]

例如,以下查询:

top 10 $m.severity, count() as $d.number_of_severities by avg($d.duration) as $d.avg_duration

将产生以下形式的日志:

[
   { "severity": "Debug", "number_of_severities":  10, avg_duration: 2000 }
   { "severity": "Warning", "number_of_severities": 50, avg_duration: 1000 },
   ...
]

您可以应用 聚合函数。

union

union 命令将两个或多个数据集的结果合并为一个数据集。 这样,用户就可以将多个查询结果合并为一个无缝数据集。 一个数据集可以是导入 union 命令的结果集,然后与另一个数据集连接。

当您需要将一个数据集的记录追加到另一个数据集时,请使用 union。

处理大型数据集时,为优化性能,可考虑在使用 union 之前使用 filter 限制每个数据集的行数。

对于 优先级洞察 数据,用户每次查询最多只能使用 10 个 union 命令。 其他数据没有限制。

unionjoin

  • union 通过将一个数据集的记录追加到另一个数据集来合并结果集。 它不会合并或比较多个文件中的列。

  • join 根据条件匹配和合并两个表中的列,创建包含两个表数据的行。

<query> | union <query>

合并 2 个数据集的示例

您有这两个数据集:

日志 Team 58942

{ "id": "111", "name": "John" , "team.id": "58942" }
{ "id": "222", "name": "Emily", "team.id": "58942" }
{ "id": "333", "name": "Alice", "team.id": "58942" }

日志 Team 98361

{ "userid": "111", "timestamp": "2022-01-01T12:00:00Z", "team.id": "98361" }
{ "userid": "111", "timestamp": "2022-01-01T12:30:00Z", "team.id": "98361" }
{ "userid": "222", "timestamp": "2022-01-01T13:00:00Z", "team.id": "98361" }
{ "userid": "222", "timestamp": "2022-01-01T13:00:00Z", "team.id": "98361" }
{ "userid": "222", "timestamp": "2022-01-01T13:00:00Z", "team.id": "98361" }

您想把它们合并成一个数据集。 您可以使用 union

source logs(teamId=58942) | union logs(teamId=98361)

查询会处理这两个数据集:

  • source logs(teamId=58942):检索所有文件 Team 58942
  • union logs (teamID=98361):将 Team 98361 数据集追加到 Team 58942 数据集中

这将产生以下数据集:

{ "id": "111", "name": "John" , "team.id": "58942" }
{ "id": "222", "name": "Emily", "team.id": "58942" }
{ "id": "333", "name": "Alice", "team.id": "58942" }
{ "userid": "111", "timestamp": "2022-01-01T12:00:00Z", "team.id": "98361" }
{ "userid": "111", "timestamp": "2022-01-01T12:30:00Z", "team.id": "98361" }
{ "userid": "222", "timestamp": "2022-01-01T13:00:00Z", "team.id": "98361" }
{ "userid": "222", "timestamp": "2022-01-01T13:00:00Z", "team.id": "98361" }
{ "userid": "222", "timestamp": "2022-01-01T13:00:00Z", "team.id": "98361" }