完全指南:用 Range Vector 聚合把日志变成可监控的指标)
Loki LogQL 指标查询Metric Queries完全指南用 Range Vector 聚合把日志变成可监控的指标【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki导读LogQL 的指标查询Metric Query是在日志查询Log Query结果之上套用聚合函数从而把「日志流」转化为「指标序列」是 Grafana Loki 中像 Prometheus 一样查询日志的核心能力。本文围绕 docs/sources/query/metric_queries.md 展开系统讲解日志范围聚合Log Range Aggregation、解包范围聚合Unwrapped Range Aggregation、内置聚合操作符与概率聚合函数approx_count_distinct/approx_topk的完整语法、可用函数清单与实战示例并结合pkg/logql/syntax等源码说明其解析与执行原理。读完本文你将能够直接用 LogQL 计算错误率、日志吞吐量、延迟分位数等指标并写出可稳定运行、可被查询分片优化的指标查询。指标查询是什么指标查询扩展了日志查询它把函数应用到日志查询的结果之上从而从日志中创造指标。这是 LogQL 最强大的特性之一。指标查询可用于计算错误消息的出现速率找出过去 3 小时内产生日志量最多的 Top N 日志源与解析器parser组合从日志行内的某个样本值如延迟、请求大小计算指标。所有标签Label包括解析器提取出的标签都可以用于聚合以及生成新的序列。从语法结构看指标查询对应 LogQL 语法树中的SampleExpr而日志查询对应LogSelectorExpr。在 pkg/logql/syntax/ast.go 中VectorAggregationExpr与RangeAggregationExpr都实现了SampleExpr接口——指标查询与日志查询在语法层被明确区分但日志查询是构成指标查询的底层材料。Range Vector 聚合LogQL 沿用了 Prometheus 的 Range Vector范围向量 概念。在 Grafana Loki 中所选取的样本范围是一段日志或标签值的范围聚合则作用在**一段时长duration**之上。Loki 使用与 Prometheus 相同语法的时间时长Time Duration例如5m、1h30m。Loki 支持两种范围向量聚合日志范围聚合Log range aggregations以日志行本身为样本解包范围聚合Unwrapped range aggregations以从日志中提取的某个标签值作为样本值。在源码 pkg/logql/syntax/ast.go 中LogRangeExpr结构体完整刻画了范围向量的组成Left日志选择器/流水线、Interval区间时长、Offset时间偏移与可选的Unwrap解包表达式。日志范围聚合日志范围聚合是「日志查询 时长」的形式把函数应用到该时长内的日志行上进行聚合。时长既可以放在日志流选择器之后也可以放在日志流水线pipeline的末尾。可用的函数函数作用rate(log-range)计算每秒的日志条目数entries per secondcount_over_time(log-range)统计给定范围内每个日志流的条目数bytes_rate(log-range)计算每个日志流每秒的字节数bytes_over_time(log-range)统计给定范围内每个日志流消耗的字节总量absent_over_time(log-range)若传入的范围向量有元素则返回空向量若无元素则返回值为 1 的单元素向量常用于一段时间内某个标签组合没有日志流/时间序列的告警这些操作符在语法层被统一定义为常量见 pkg/logql/syntax/ast.go 中的OpRangeTypeRate、OpRangeTypeCount、OpRangeTypeBytes、OpRangeTypeBytesRate、OpRangeTypeAbsent等。示例统计 MySQL 任务最近 5 分钟内的全部日志行count_over_time({jobmysql}[5m])示例带过滤器和解析器的聚合——计算 MySQL 任务最近 1 分钟内、按 host 分组的每秒非超时错误速率且只统计时长大于 10 秒的错误sum by (host) (rate({jobmysql} | error ! timeout | json | duration 10s [1m]))注意这个查询把时长[1m]放在整个流水线流选择器 行过滤器 JSON 解析器 标签过滤器之后——这正是上文所说的时长可放在日志流水线末尾。从执行层面看日志范围聚合对应的样本提取器在 pkg/logql/syntax/extractor.go 中实现rate、count_over_time、absent_over_time走log.CountExtractor按行计数bytes_rate、bytes_over_time走log.BytesExtractor按字节计量。Offset 修饰符offset修饰符允许改变查询中单个范围向量的时间偏移。例如下面这个表达式统计的是 MySQL 任务「最近 10 分钟到最近 5 分钟」之间的日志即往前偏移 5 分钟而不是最近 5 分钟。注意offset修饰符必须紧跟范围向量选择器。count_over_time({jobmysql}[5m] offset 5m) // GOOD count_over_time({jobmysql}[5m]) offset 5m // INVALID第二行的写法是非法的因为offset被放在了整个count_over_time(...)表达式之外。在语法树中偏移量作为OffsetExpr见 pkg/logql/syntax/ast.go内嵌于LogRangeExpr中这也从结构上保证了offset只能紧邻范围向量。解包范围聚合解包范围Unwrapped range使用提取出的标签作为样本值而不是使用日志行本身。要选择哪个标签参与聚合日志查询必须以unwrap表达式结尾并可附带一个可选的标签过滤表达式来丢弃错误样本见下文__error__说明。unwrap表达式的记法是| unwrap label_identifier其中label_identifier就是用于提取样本值的标签名。由于标签值本质上是字符串默认情况下 Loki 会尝试将其转换为64 位浮点数float64转换失败时采样会被打上__error__标签Pipeline Errors可参考 日志查询文档 中的相关说明实践中通常用| __error__ 过滤掉这类样本见 query_examples.md 中的 unwrap 示例。可选地标签标识符可以被转换函数包裹| unwrap function(label_identifier)从而按特定格式解析标签值。目前支持两种转换函数duration_seconds(label_identifier)简写duration按 Go 时长格式 把标签值转换为秒例如5m、24s30msbytes(label_identifier)按字节单位把标签值转换为原始字节数例如5 MiB、3k、1G。在源码 pkg/logql/syntax/extractor.go 中解包转换被映射到对应的转换操作bytes对应log.ConvertBytesduration/duration_seconds对应log.ConvertDuration默认情况使用log.ConvertFloat。支持作用于解包范围上的聚合函数函数作用rate(unwrapped-range)计算指定区间内所有值之和的每秒速率rate_counter(unwrapped-range)计算指定区间内值的每秒速率并按计数器指标counter metric处理sum_over_time(unwrapped-range)指定区间内所有值的和avg_over_time(unwrapped-range)指定区间内所有点的平均值max_over_time(unwrapped-range)指定区间内所有点的最大值min_over_time(unwrapped-range)指定区间内所有点的最小值first_over_time(unwrapped-range)指定区间内第一个值last_over_time(unwrapped-range)指定区间内最后一个值stdvar_over_time(unwrapped-range)指定区间内值的总体方差population variancestddev_over_time(unwrapped-range)指定区间内值的总体标准差population standard deviationquantile_over_time(scalar, unwrapped-range)指定区间内值的 φ 分位数0 ≤ φ ≤ 1absent_over_time(unwrapped-range)传入的范围向量有元素则返回空向量无元素则返回值为 1 的单元素向量常用于告警除sum_over_time、absent_over_time、rate和rate_counter之外解包范围聚合支持分组aggr-op([parameter,] unwrapped-range) [without|by (label list)]without从结果向量中移除列出的标签保留其余标签by则相反丢弃未在by子句中列出的标签——即使这些标签在所有元素之间取值完全相同也会被丢弃。这一约束在语法校验中也有体现在 pkg/logql/syntax/ast.go 的RangeAggregationExpr.Validate()中sum_over_time、rate、rate_counter、count_over_time等被明确排除在允许 grouping的操作之外而avg_over_time、stddev_over_time、quantile_over_time、max/min/first/last_over_time等才允许分组。示例按 path 计算 nginx-ingress 延迟的 p99quantile_over_time(0.99, {clusterops-tools1,containeringress-nginx} | json | __error__ | unwrap request_time [1m]) by (path)示例按组织 ID 统计处理的字节总量sum by (org_id) ( sum_over_time( {clusterops-tools1,containerloki-dev} | metrics.go | logfmt | unwrap bytes_processed [1m]) )更多 unwrap 用法可参考 query_examples.md 中的 Unwrap 示例。另外需要说明quantile_over_time等分位数聚合在查询分片sharding场景下有特殊限制。从源码 pkg/logql/syntax/ast.go 看当quantile_over_time不是查询最外层聚合例如被max(...)包裹时会阻止该查询被分片执行以保证结果的正确性。内置聚合操作符与 PromQL 类似LogQL 支持一组内置聚合操作符可以对单个向量中的元素进行聚合产出一个元素更少但值被聚合的新向量sum按标签求和avg按标签求平均min按标签取最小值max按标签取最大值stddev按标签计算总体标准差stdvar按标签计算总体方差count统计向量中的元素个数topk按样本值选取最大的 k 个元素bottomk按样本值选取最小的 k 个元素sort按样本值升序排列向量元素sort_desc同sort但按降序排列聚合操作符既可以对全部标签值聚合也可以通过without或by子句对一组不同的标签值聚合aggr-op([parameter,] vector expression) [without|by (label list)]关键规则使用topk和bottomk时必须提供parameter即 k 值topk和bottomk与其他聚合器不同它们在结果向量中返回的是输入样本的一个子集并保留原始标签by和without仅用于对输入向量分组without从结果向量中移除列出的标签、保留其余标签by则丢弃未列出的标签即使这些标签值在所有元素之间相同也会被丢弃。从源码 pkg/logql/syntax/ast.go 的mustNewVectorAggregationExpr可以看到topk/bottomk必须有参数且参数必须大于 0其他操作符不允许带参数OpTypeSort、OpTypeSortDesc等常量也在 ast.go 中统一定义。示例按日志吞吐量最高的前 10 个应用topk(10,sum(rate({regionus-east1}[5m])) by (name))示例统计指定任务最近 5 分钟按 level 分组的日志行数sum(count_over_time({jobmysql}[5m])) by (level)示例按 region 计算 NGINX 日志中指向/home路径的 GET 请求速率avg(rate(({jobnginx} | GET | json | path/home)[10s])) by (region)更多向量聚合示例见 query_examples.md 中的 Vector aggregation 示例。函数vectorLogQL 还支持一组内置函数其中最重要的是vector(s scalar)它返回一个无标签的向量值为标量s。其行为与 Prometheus 的vector()函数一致主要用于为原本无返回值的序列提供一个返回值在基于 LogQL 定义告警时非常有用。示例traefik 命名空间最近 5 分钟日志行数sum(count_over_time({namespacetraefik}[5m])) # will return nothing or vector(0) # will return 0当sum(count_over_time(...))没有任何匹配的日志流时返回空配合or vector(0)可以兜底返回 0从而让告警表达式始终有值可计算。概率聚合Probabilistic aggregation当聚合基数cardinality极高时精确聚合可能导致序列数爆炸。LogQL 为此提供了两个概率近似聚合函数。approx_count_distinctapprox_count_distinct近似统计某个标签或提取字段的去重值数量而不会为每个去重值创建一条序列。当精确的count by会导致序列基数爆炸时应改用此函数。启用前置条件在 Loki 配置 的limits_config.shard_aggregations中加入approx_count_distinct同时要求frontend.encoding: protobuf。对应配置项在源码 pkg/validation/limits.go 中定义为ShardAggregations []stringyaml 键名shard_aggregations文档注释为List of LogQL vector and range aggregations that should be sharded即应被分片的 LogQL 向量与范围聚合列表。函数形式approx_count_distinct( counted field, log expression [duration] ) [by (grouping fields)]示例按版本分组统计过去 1 天出现过的 MAC 地址去重数approx_count_distinct( mac_address, {jobdevices} | json [1d] ) by (version)使用约束支持即时查询instant与范围查询range必须带范围时长分组可选省略by时保留剩余的流标签使用by ()时产出单条无标签序列不要按被计数字段进行分组。实现原理Loki 为每个输出分组构建一个精度为 14 的 HyperLogLog 草图sketch低基数时稀疏、密集时约 16 KiB。索引分片Index sharding会在估算前合并各分片的草图从而让跨分片的重叠值只被计数一次。approx_topk注意approx_topk是实验性功能无工程与值班支持文档有限、无 SLA。使用前需在limits_config.shard_aggregations中加入approx_topk若要在 Grafana Cloud 中启用请联系 Grafana Support。approx_topk是topk的概率近似版本可作为topk的即插即用替代品drop-in replacement。当topk查询超时或触及最大序列数限制通常发生在候选值列表极大、需要从中找出最频繁值的时候时approx_topk尤为有用它也适用于更快的近似答案优于更慢的精确答案的场景。函数形式approx_topk(k, vector expression)使用约束仅支持即时查询instant queries不支持分组grouping应通过内部的sum by或sum without处理分组——尽管这可能与topk by的行为不完全一致。实现原理approx_topk基于分片实现。每个分片使用 Count-Min Sketch 算法和堆heap来近似计数近似精度取决于堆的大小由 Loki 的max_count_min_sketch_heap_size参数定义默认 10,000当k接近堆大小时精度会下降。该参数在源码 pkg/logql/engine.go 中定义max_count_min_sketch_heap_size命令行前缀为logql.max-count-min-sketch-heap-size默认值10_000含义是使用 count-min-sketch 的 topk 查询堆最多可跟踪的标签数量。表达式approx_topk(k, inner)在内部会被改写为topk( k, eval_cms( __count_min_sketch__(inner, shard1) __count_min_sketch__(inner, shard2)... ) )即每个分片计算__count_min_sketch__在 frontend 合并然后eval_cms遍历标签列表确定每个标签的计数最后由topk选出前 k 项。结果排序Result ordering指标查询的返回结果不保证任何特定顺序除非查询使用了sort或sort_desc它们按样本值排序。除此之外不要依赖结果的顺序。另外注意sort和sort_desc只影响**即时查询instant query**的结果范围查询range query的结果顺序即使使用了sort/sort_desc也是未指定的。进一步阅读理解日志查询与解析器pipeline、__error__标签等可阅读 日志查询文档大量可直接复用的完整查询示例见 query_examples.md指标查询的语法解析与 AST 结构可深入 pkg/logql/syntax/ast.go 与 pkg/logql/syntax/extractor.go概率聚合所需的分片与限制配置见 pkg/validation/limits.go 与 pkg/logql/engine.goLoki 官方社区也曾举办过How to turn logs into metrics with Grafana Loki专题分享Loki Community Call, July 2025可作延伸学习。【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考