菜单
开源 RSS

LogQL:日志查询语言

LogQL 是 Grafana Loki 受到 PromQL 启发的查询语言。查询的作用类似于分布式的 grep,用于聚合日志源。LogQL 使用标签和操作符进行过滤。

LogQL 查询有两种类型

二元操作符

算术操作符

Loki 中存在以下二元算术操作符

  • + (加)
  • - (减)
  • * (乘)
  • / (除)
  • % (模)
  • ^ (幂)

二元算术操作符定义在两个字面量(标量)、一个字面量和一个向量、以及两个向量之间。

在两个字面量之间,行为很明显:它们计算为另一个字面量,该字面量是将操作符应用于两个标量操作数的结果(1 + 1 = 2)。

在向量和字面量之间,操作符应用于向量中每个数据样本的值,例如,如果时间序列向量乘以 2,结果将是另一个向量,其中原始向量的每个样本值都乘以 2。

在两个向量之间,二元算术操作符应用于左侧向量中的每个条目及其在右侧向量中的匹配元素。结果被传播到结果向量中,分组标签成为输出标签集。在右侧向量中找不到匹配条目的条目不属于结果。

链接算术操作符时,请特别注意操作符顺序

算术示例

使用简单查询实现健康检查

logql
1 + 1

将日志流的速率加倍

logql
sum(rate({app="foo"}[1m])) * 2

获取 foo 应用警告日志与错误日志的比例

logql
sum(rate({app="foo", level="warn"}[1m])) / sum(rate({app="foo", level="error"}[1m]))

逻辑和集合操作符

这些逻辑/集合二元操作符仅定义在两个向量之间

  • and (交集)
  • or (并集)
  • unless (补集)

vector1 and vector2 的结果是一个向量,其中包含 vector1 的元素,这些元素在 vector2 中具有完全匹配标签集的对应元素。其他元素被丢弃。

vector1 or vector2 的结果是一个向量,其中包含 vector1 的所有原始元素(标签集 + 值),以及 vector2 中所有在 vector1 中没有匹配标签集的额外元素。

vector1 unless vector2 的结果是一个向量,其中包含 vector1 的元素,这些元素在 vector2 中没有完全匹配标签集的对应元素。两个向量中所有匹配的元素都被丢弃。

二元操作符示例

这个构造的查询将返回这些查询的交集,实际效果相当于 rate({app="bar"})

logql
rate({app=~"foo|bar"}[1m]) and rate({app="bar"}[1m])

比较操作符

  • == (等于)
  • != (不等于)
  • > (大于)
  • >= (大于或等于)
  • < (小于)
  • <= (小于或等于)

比较操作符定义在标量/标量、向量/标量和向量/向量值对之间。默认情况下,它们起过滤作用。通过在操作符后提供 bool 可以修改其行为,这将返回 0 或 1 作为值而不是过滤。

在两个标量之间,这些操作符产生另一个标量,根据比较结果,该标量为 0 (假) 或 1 (真)。**不得**提供 bool 修饰符。

1 >= 1 等同于 1

在向量和标量之间,这些操作符应用于向量中每个数据样本的值,并且比较结果为假的向量元素将从结果向量中被丢弃。如果提供了 bool 修饰符,则将被丢弃的向量元素的值变为 0,而将被保留的向量元素的值为 1。

过滤在最后一分钟内至少记录了 10 行的流

logql
count_over_time({foo="bar"}[1m]) > 10

为记录少于/多于 10 行的流附加值 0/1

logql
count_over_time({foo="bar"}[1m]) > bool 10

在两个向量之间,这些操作符默认作为过滤器,应用于匹配的条目。表达式不为真或在表达式另一侧找不到匹配的向量元素将从结果中被丢弃,而其他元素将被传播到结果向量中。如果提供了 bool 修饰符,则将被丢弃的向量元素的值变为 0,而将被保留的向量元素的值为 1,分组标签再次成为输出标签集。

返回匹配 app=foo 且不包含 app 标签的流,这些流在最后一分钟内的计数高于匹配 app=bar 且不包含 app 标签的对应流

logql
sum without(app) (count_over_time({app="foo"}[1m])) > sum without(app) (count_over_time({app="bar"}[1m]))

与上述相同,但如果向量通过比较则将其值设置为 1,如果未通过/否则会被过滤掉则设置为 0

logql
sum without(app) (count_over_time({app="foo"}[1m])) > bool sum without(app) (count_over_time({app="bar"}[1m]))

模式匹配过滤器操作符

  • |> (行匹配模式)
  • !> (行不匹配模式)

模式过滤器不仅提高了效率,还简化了 LogQL 查询的编写过程。通过消除复杂正则表达式的需要,用户可以使用更直观的语法创建查询,从而降低认知负担和潜在错误。

在模式语法中,<_> 用作通配符,表示任何任意文本。这允许查询匹配出现指定模式的日志行,例如包含静态内容和中间可变内容的日志行。

行匹配模式示例

logql
{service_name=`distributor`} |> `<_> caller=http.go:194 level=debug <_> msg="POST /push.v1.PusherService/Push <_>`

行不匹配模式示例

logql
{service_name=`distributor`} !> `<_> caller=http.go:194 level=debug <_> msg="POST /push.v1.PusherService/Push <_>`

例如,上面的示例查询将分别匹配和不匹配来自 distributor 服务的以下日志行

log
ts=2024-04-05T08:40:13.585911094Z caller=http.go:194 level=debug traceID=23e54a271db607cc orgID=3648 msg="POST /push.v1.PusherService/Push (200) 12.684035ms"
ts=2024-04-05T08:41:06.551403339Z caller=http.go:194 level=debug traceID=54325a1a15b42e2d orgID=1218 msg="POST /push.v1.PusherService/Push (200) 1.664285ms"
ts=2024-04-05T08:41:06.506524777Z caller=http.go:194 level=debug traceID=69d4271da1595bcb orgID=1218 msg="POST /push.v1.PusherService/Push (200) 1.783818ms"
ts=2024-04-05T08:41:06.473740396Z caller=http.go:194 level=debug traceID=3b8ec973e6397814 orgID=3648 msg="POST /push.v1.PusherService/Push (200) 1.893987ms"
ts=2024-04-05T08:41:05.88999067Z caller=http.go:194 level=debug traceID=6892d7ef67b4d65c orgID=3648 msg="POST /push.v1.PusherService/Push (200) 2.314337ms"
ts=2024-04-05T08:41:05.826266414Z caller=http.go:194 level=debug traceID=0bb76e910cfd008d orgID=3648 msg="POST /push.v1.PusherService/Push (200) 3.625744ms"

操作符顺序

链接或组合操作符时,您必须考虑操作符优先级:通常,您可以遵循常规的数学约定,同一优先级的操作符为左结合。

更多详细信息可以在Golang 语言文档中找到。

1 + 2 / 3 等于 1 + ( 2 / 3 )

2 * 3 % 2 计算为 (2 * 3) % 2

关键字 on 和 ignoring

ignoring 关键字使得在匹配期间忽略指定的标签。语法为

logql
<vector expr> <bin-op> ignoring(<labels>) <vector expr>

此示例将返回在最后一分钟内总计数超过应用 foo 平均值的机器。

logql
max by(machine) (count_over_time({app="foo"}[1m])) > bool ignoring(machine) avg(count_over_time({app="foo"}[1m]))

on 关键字将考虑的标签集减少到指定的列表。语法为

logql
<vector expr> <bin-op> on(<labels>) <vector expr>

此示例将返回应用 foo 中每台机器在最后一分钟内的总计数比率

logql
sum by(machine) (count_over_time({app="foo"}[1m])) / on() sum(count_over_time({app="foo"}[1m]))

多对一和一对多向量匹配

多对一和一对多匹配发生在“一”侧的每个向量元素可以与“多”侧的多个元素匹配时。您必须使用 group_left 或 group_right 修饰符明确请求匹配,其中 left 或 right 决定哪个向量具有更高的基数。语法为

logql
<vector expr> <bin-op> ignoring(<labels>) group_left(<labels>) <vector expr>
<vector expr> <bin-op> ignoring(<labels>) group_right(<labels>) <vector expr>
<vector expr> <bin-op> on(<labels>) group_left(<labels>) <vector expr>
<vector expr> <bin-op> on(<labels>) group_right(<labels>) <vector expr>

group 修饰符提供的标签列表包含来自“一”侧的额外标签,这些标签将包含在结果指标中。并且一个标签应仅出现在 ongroup_x 指定的列表中的一个。结果向量中的每个时间序列必须是唯一可识别的。分组修饰符只能用于比较和算术运算。默认情况下,系统将 andunlessor 操作与右侧向量中的所有条目进行匹配。

以下示例返回按 appstatus 分区请求的速率,作为总请求的百分比。

logql
sum by (app, status) (
  rate(
    {job="http-server"}
      | json
      [5m]
  )
)
/ on (app) group_left
sum by (app) (
  rate(
    {job="http-server"}
      | json
      [5m]
  )
)

=>
[
  {app="foo", status="200"} => 0.8
  {app="foo", status="400"} => 0.1
  {app="foo", status="500"} => 0.1
]

此版本使用 group_left(<labels>) 将右侧的 <labels> 包含在结果中,并返回每个用户、组织和命名空间的丢弃事件成本

logql
sum by (user, namespace) (
  rate(
    {job="events"}
      | logfmt
      | discarded="true"
      [5m]
  )
)
* on (user) group_left(organization)
max_over_time(
  {job="cost-calculator"}
    | logfmt
    | unwrap cost
    [5m]
) by (user, organization)

=>
[
  {user="foo", namespace="dev", organization="little-org"} => 10
  {user="foo", namespace="prod", organization="little-org"} => 50
  {user="bar", namespace="dev", organization="big-org"} => 70
  {user="bar", namespace="prod", organization="big-org"} => 200
]

注释

LogQL 查询可以使用 # 字符进行注释

logql
{app="foo"} # anything that comes after will not be interpreted in your query

对于多行 LogQL 查询,查询解析器可以使用 # 排除整行或部分行

logql
{app="foo"}
    | json
    # this line will be ignored
    | bar="baz" # this checks if bar = "baz"

管道错误

导致管道处理错误的原因有多种,例如

  • 数值标签过滤器可能无法将标签值转换为数字
  • 标签的指标转换可能失败。
  • 日志行不是有效的 JSON 文档。
  • 等等...

当这些失败发生时,Loki 不会过滤掉这些日志行。相反,它们会被传递到管道的下一阶段,并带有一个新的系统标签 __error__。过滤掉错误的唯一方法是使用标签过滤表达式。__error__ 标签无法通过语言进行重命名。

例如,要移除 JSON 错误

logql
  {cluster="ops-tools1",container="ingress-nginx"}
    | json
    | __error__ != "JSONParserErr"

或者,您可以使用通配符匹配器移除所有错误,例如 __error__ = "",甚至只使用 __error__ != "" 显示错误。

过滤器应放置在生成此错误的阶段之后。这意味着如果您需要移除 unwrap 表达式中的错误,它需要放在 unwrap 之后。

logql
quantile_over_time(
	0.99,
	{container="ingress-nginx",service="hosted-grafana"}
	| json
	| unwrap response_latency_seconds
	| __error__=""[1m]
	) by (cluster)

指标查询不能包含错误,如果在执行过程中发现错误,Loki 将返回错误和相应的状态码。

函数

Loki 支持对数据进行操作的函数。

label_replace()

对于 v 中的每个时间序列,

label_replace(v instant-vector,
    dst_label string,
    replacement string,
    src_label string,
    regex string)

将正则表达式 regex 与标签 src_label 进行匹配。如果匹配成功,则返回该时间序列,其中标签 dst_labelreplacement 的扩展替换。

$1 被第一个匹配的子组替换,$2 被第二个替换,依此类推。如果正则表达式不匹配,则时间序列保持不变地返回。

此示例将返回一个向量,其中每个时间序列都添加了一个值为 afoo 标签

logql
label_replace(rate({job="api-server",service="a:c"} |= "err" [1m]), "foo", "$1",
  "service", "(.*):.*")