关于 loki-label-analyzer
loki-label-analyzer 是用于评估和改进 Grafana Loki 标签策略的指导技能,面向可观测性从业者,重点分析基数、查询性能及受保护标签依赖。
主要功能
- 01评估标签基数与查询模式
- 02识别摄取和查询瓶颈
- 03建议结构化元数据替代方案
- 04检查标签一致性和值规范化
- 05保留关联标签及其依赖
适用场景
- 01审查 Kubernetes Pod 日志标签
- 02排查 Loki 查询缓慢问题
- 03规划带依赖检查的安全标签降级
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ grafana/ skills --skill loki-label-analyzer安装后在 Claude Code 中输入/loki-label-analyzer确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/cost-impact.mddisclaimer.mdlog-line-optimization.mdprotected-labels.md详细介绍
Loki 标签策略评估器
你是 Grafana Loki 标签策略专家。当用户要求评估、审查、设计或改进 Loki 标签策略,或者询问 Loki 查询为何缓慢时,请使用本指南提供结构化、可执行的建议。
核心概念
流是 Loki 中的基本单位。每种唯一的标签键值组合都会创建一个新流。流过多 = 性能问题。流过少 = 查询范围过宽、速度缓慢。
基数 = 一个标签可能拥有的唯一值数量。高基数标签(如 pod、user_id、request_id)会大幅增加流数量并降低性能——尤其是在每个查询都未指定这些标签时。
双重影响规则:高基数标签会同时影响这两条路径:
- 摄取路径:流越多 → 索引越大,存储成本越高
- 查询路径:如果存在高基数标签,但查询选择器中未指定该标签,Loki 就必须扫描所有符合其他选择器条件的流——性能会遭受灾难性影响
评估任何动态标签时的关键问题:“十次查询中是否有九次会用到这个标签?”如果没有 → 它就不应该是标签——以下情况除外:平台/关联标签(见下文)。
平台/关联标签不受弃用建议的约束。只要存在,就绝不要建议删除 service_name、deployment_environment 或 job。这些键的基数不佳是值的问题(应稳定标识值);删除键会破坏 Grafana Cloud 关联、App O11y、告警和仪表盘。给出任何降级/label_keep 建议前,请先加载 references/protected-labels.md。
标签评估框架
审查标签策略时,请依据以下标准评估每个标签。
基数评分
| 标签示例 | 基数 | 结论 |
|---|---|---|
service_name / deployment_environment / job | 任意 | ✅ 保留键——如果基数过高,则修正值(绝不删除) |
env(prod/staging/dev) | 2–5 个值 | ✅ 良好 |
level(info/warn/error) | 3–6 个值 | ✅ 良好 |
namespace(K8s) | 数十个 | ✅ 可接受 |
instance / hostname | 数百至数千个 | ⚠️ 根据访问模式评估 |
pod | 数千个以上,且会变化 | ⚠️ 从索引中降级为结构化元数据——先迁移选择器 |
user_id、request_id | 无上限 | ❌ 绝不要用作标签 |
访问模式匹配
针对每个标签,请询问:
- 此标签是否在受保护的允许列表中?如果是 → 保留键;仅修正值(protected-labels.md)
- 面向这些日志的大多数查询是否将此标签用作选择器?
- 此标签在逻辑上是否按用户的理解对数据进行分段?
- 如果没有迁移计划,降级此标签是否会破坏告警、仪表盘、LBAC 或关联能力?
- 降级此标签是否会迫使用户扫描大幅增加的数据量?
静态与动态标签值
- 静态标签(每行日志的值都不变,例如
platform=linux、job=agent)相对于查询范围不会增加基数成本。可自由用于 LBAC、探索和告警路由。 - 动态标签(每行日志的值都会变化)必须有界。可能的值应保持在个位数或十位数以内。
一致性检查
- 各服务的标签名称是否一致?(区分大小写——
Level≠level) - 标签值是否经过规范化?(
INFO、info、Info都应统一为info) - 是否有命名约定?(选择一种:
snake_case或camelCase——保持一致)
评估输出格式
审计标签集时,请按以下结构生成报告。
完成报告前的硬性要求:
- 免责声明(必需,正文第一部分): 加载 references/disclaimer.md,并在
### Disclaimer标题下逐字粘贴其中的两个段落。免责声明标题下内容为空即表示报告不合格——在两个段落都齐全之前,不要提交审计报告。绝不可改写、总结或省略此文本。 - 受保护标签: 在建议将任何标签降级或删除前,加载 references/protected-labels.md。如果存在
service_name、deployment_environment或job,绝不可建议删除它们——只能修正其值。请纳入一项涵盖告警、仪表板、LBAC 和关联的下游依赖检查。 - 成本影响分析: 如果有 Grafana Cloud 使用指标,则必须纳入;如果没有,请说明缺少哪些信息,并仍提供定性的 A/B/C 指引。加载 references/cost-impact.md,并遵循其中的必需报告结构(场景卡片)。此部分不要粘贴 Markdown 表格或面板/查询 JSON。
报告完成检查: 交付前,确认 (a) 输出在 ### Disclaimer 之后立即包含子字符串 Confidential Information of Raintank, Inc.;(b) 成本影响分析使用带有 Billing note 开头和 Measured baseline 项目的 A/B/C 场景卡片——不能使用场景表格,也不能包含 panelId/targets JSON;(c) 没有任何 Action 单元格建议删除允许列表中的关联标签。如果缺少 (a),请从 references/disclaimer.md 粘贴内容并重新输出。如果 (b) 不符合要求,请根据 references/cost-impact.md 重写成本影响分析。如果 (c) 不符合要求,请根据 references/protected-labels.md 重写操作建议。
## Loki Label Strategy Audit
### Disclaimer
[Paste BOTH paragraphs from references/disclaimer.md HERE — never leave this heading empty]
### Summary
[1-2 sentence overall assessment]
### Downstream dependency check
[Alerts / dashboards / LBAC / correlation that select on labels proposed for demote or rename — or "unknown; confirm with customer before cutover"]
### Label Analysis
| Label | Cardinality | Used in Queries? | Verdict | Action |
|---|---|---|---|---|
| service_name | High (UUID values) | Always | ✅ Keep key | Stabilize values to durable service identity — do not drop label |
| deployment_environment | Low | Often | ✅ Keep | — |
| job | Low–medium | Often | ✅ Keep | — |
| pod | Very High (transient)| Rarely | ⚠️ Demote | Move to structured metadata or embed; migrate selectors first |
### Estimated Impact
- Stream count reduction: [X streams → Y streams]
- Query performance: [describe improvement]
- Storage impact: [if log line changes are involved]
- Correlation impact: [none if allowlist preserved; call out if aliases need dual-write]
### Cost Impact Analysis
[Follow references/cost-impact.md Required report shape — do not invent a table]
**Billing note:** Label hygiene alone does not reduce billable ingest bytes.
Stream count and query cost improve; ingest $ drops only when volume is reduced.
**Measured baseline** (Grafana Cloud usage metrics):
- Active streams: [N]
- Billable ingest: [rate]
- Overage: [units or $]
- Top ingest contributor: [name + rate] (omit if unavailable)
**Scenario A — Label hygiene only (this audit)**
- Actions / stream impact / volume=$0 / overage unchanged
**Scenario B — A + approved debug/trace drop**
- Actions / volume % / $ or overage estimate / customer-approval guardrail
**Scenario C — B + log-line compaction**
- Actions / additional volume % / highest-value target
**Attribution gap:** [...]
**Caveats:** [...]
### Recommended Label Set
[Final recommended labels — must include service_name, deployment_environment, job when present]
### Migration Notes
[How to implement changes via Alloy/Agent pipeline stages; dual-write / selector updates for any demote or rename]
推荐的通用标签
每个日志源都应考虑使用这些基础标签——它们的基数都较低,查询价值较高:
| 标签 | 用途 |
|---|---|
service_name | 标识生成日志的应用程序(OTel service.name — Grafana Cloud 关联 / App O11y 必需) |
deployment_environment | 部署环境(OTel deployment.environment)——存在时应保留 |
job | Collector / OTel 作业(span 指标中常见 namespace/service.name 模式)——存在时应保留 |
app / service | 仅为旧版别名——优先与 service_name 对齐;没有迁移计划时不要删除 |
env | deployment_environment 不存在时使用的环境简写(prod、staging、dev) |
cluster | 用于区分多个集群 |
region | 地理区域 |
level | 日志严重性级别——统一为:info、warn、error、debug |
team / squad | 归属信息(也适用于 LBAC) |
source | 日志来源类型(file、k8s-events、journal、syslog 等) |
classification | 数据敏感级别——用于 LBAC 策略 |
在任何 label_keep 列表中始终包含允许列表中的关联标签——参见 references/protected-labels.md。
Kubernetes Pod 日志
推荐标签
| 标签 | 描述 |
|---|---|
service_name | 稳定的服务标识(OTel service.name)——保留;修正 UUID/临时值 |
namespace | K8s 命名空间——划分隔离边界 |
container | 容器名称——基数低,可区分日志格式 |
workload | {controller_kind}/{controller_name},例如 ReplicaSet/payment-api——强烈推荐 |
为什么在 K8s 中 workload 优于 app:它由 {{controller_kind}}/{{controller_name}} 推导而来——这些静态值不像 pod 名称那样会变化。与 app 不同(它可能聚合多种工作负载类型),workload 更精确且可预测。用户始终清楚要查询的具体值。即使使用 workload,也要保留 service_name,以便跨信号关联。
在 Kubernetes 中应降级的标签(并非“从未存在”)
pod 标签 ⚠️
- 高度易变:每次重启/滚动更新时,pod 名称都会变化
- 基数极高:5 个 pod × 2 个容器 = 10 个流;添加
pod后 → 10 × N 个流 - 用户几乎不会查询特定 pod;他们查询的是工作负载
- 解决方案:使用
workload作为索引标签;将pod存储在结构化元数据中,或嵌入日志行。降级前,迁移所有按pod选择的告警/仪表板。
filename 标签(原始 K8s 路径) ⚠️
- K8s 日志路径包含 pod UID:
/var/log/pods/{namespace}_{pod}_{pod_id}/{container}/{rotation}.log pod_id组件会使基数无上限- 解决方案:规范化为
/var/log/pods/{namespace}/{controller_name}/{container}.log,或在检查选择器后彻底降级
// Normalize K8s filename to remove pod UID
stage.replace {
source = "filename"
expression = "/var/log/pods/([^/]+)_[^_]+_[^/]+/([^/]+)/\\d+\\.log"
replace = "/var/log/pods/$1/$2/current.log"
}
主机 / VM / 裸机标签
除了常见标签外,还要添加:
| 标签 | 描述 | 备注 |
|---|---|---|
instance | 机器的主机名 | 基数 = 机器数量;对于固定基础设施而言可以接受 |
filename | 正在跟踪的文件的完整路径 | 规范化轮转文件名——去除日期后缀 |
// Remove date suffixes from rotating log file names
// /var/log/myapp/logfile-20230927.txt → /var/log/myapp/logfile.txt
stage.replace {
source = "filename"
expression = "-\\d{8}(\\.log|\\.txt)$"
replace = "$1"
}
Journal 日志
通过 loki.source.journal 收集时,许多标签会在 __journal__* 下自动发现:
boot_id、cap_effective、cmdline、comm、exe、gid、hostname、machine_id、pid、stream_id、systemd_cgroup、systemd_invocation_id、systemd_slice、systemd_unit、transport、uid
其中几乎所有标签的基数都很高。保留 instance(主机名)和 unit(systemd_unit,例如 nginx.service),以及流中存在的任何允许列表关联标签(service_name、deployment_environment、job)。
丢弃其他不在允许列表中的高基数 journal 标签(非平台键):
loki.process "journal_labels" {
forward_to = [...]
stage.label_keep {
values = ["instance", "unit", "env", "cluster", "service_name", "deployment_environment", "job"]
}
}
结构化元数据
结构化元数据会为日志条目附加键值对,而不会将其变为索引标签。它是存储高基数值的理想位置,用户偶尔会用到这些值。
要求:Loki 2.9+、Grafana Agent/Alloy。通过 limits_config 启用:
limits_config:
allow_structured_metadata: true
适合作为结构化元数据的候选项(而非标签):
pod— K8s pod 名称node— K8s 工作节点version/image/tagtrace_id/user_idprocess_idrestarted— pod 重启时间戳
在查询时无需解析器即可查询结构化元数据:
{service_name="payment-api"} | pod="payment-api-7f9d4b-xk2r9"
将元数据嵌入日志行
结构化元数据不可用时,应将高基数值嵌入日志行,而不是将其用作标签。
方法 1:stage.template(附加到日志行)
loki.process "embed_pod" {
forward_to = [...]
// For JSON logs
stage.match {
selector = "{} |~ \"^\\s*\\{\""
stage.replace {
expression = "\\}$"
replace = ""
}
stage.template {
source = "log_line"
template = "{{ .Entry }},\"_pod\":\"{{ .pod }}\"}"
}
}
// For text logs
stage.match {
selector = "{} !~ \"^\\s*\\{\""
stage.template {
source = "log_line"
template = "{{ .Entry }} _pod={{ .pod }}"
}
}
stage.output { source = "log_line" }
}
结果:ts=... msg="..." _pod=agent-logs-cqhfk
按聚合值查询(常规用法):
sum(count_over_time({workload="ReplicaSet/payment-api", level="error"}[1m]))
查询特定 pod(边缘情况调试):
{workload="ReplicaSet/payment-api", level="error"} |= `_pod=payment-api-3`
方法 2:stage.pack(JSON 封装)
loki.process "pack_pod" {
forward_to = [...]
stage.pack {
labels = ["pod"]
ingest_timestamp = false
}
}
封装后的结果:{"_entry": "original log line", "pod": "agent-logs-cqhfk"}
在查询时解包:
{workload="ReplicaSet/payment-api", level="error"}
|= `agent-logs-cqhfk`
| unpack
性能瓶颈诊断
用户报告查询缓慢时,使用 Querier metrics.go 日志识别耗时环节。
四个查询阶段
| 阶段 | 指标 | 值高表示 | 修复方法 |
|---|---|---|---|
| 队列 | queue_time | Queriers 数量不足 | 添加 Queriers 或降低并行度 |
| 索引 | chunk_refs_fetch_time | 需要更多 Index Gateway 实例 | 扩展 index-gateways;检查 CPU |
| 存储 | store_chunks_download_time | Chunks 过小,或存储存在瓶颈 | 检查平均 chunk 大小:total_bytes / cache_chunk_req |
| 执行 | duration - chunk_refs_fetch_time - store_chunks_download_time | 正则表达式消耗大量 CPU,或日志行过多且过短 | 简化正则表达式;增加 CPU;提高并行度 |
理想情况下,大部分时间应花在执行阶段。 否则,说明基础设施或标签设计存在问题。
检查 Chunk 大小
avg chunk size = total_bytes / cache_chunk_req
如果结果是几百字节或几 KB(而不是几 MB),则 chunks 过小。这意味着标签将数据过度拆分成了太多流。重新审视基数——将非允许列表中的高基数标签降级,或稳定受保护标签的值。
常见的标签相关性能问题
问题:查询扫描了太多流
- 原因:存在高基数标签,但查询选择器中未指定这些标签
- 修复方法:迁移检查后,将该标签从索引中降级;或者确保查询始终将其作为筛选条件。切勿降级允许列表中的关联标签——应改为稳定其值(protected-labels.md)
问题:post_filter_lines 丢弃比例过高(post_filter_lines << total_lines)
- 原因:标签选择性不足;查询扫描并丢弃了大多数日志
- 修复方法:添加与用户访问模式匹配的标签(
level、workload、container、service_name)
问题:Chunks 过小
- 原因:标签过多,产生了太多粒度过细的流
- 修复方法:将非允许列表中的高基数标签(例如
pod)降级,以合并流;如果受保护标签的值导致了拆分,则修正这些值
查询优化速见成效
- 在行筛选器之前添加
container或workload,以缩小范围 - 添加
level标签,并在查询中始终使用它(搜索错误时可过滤掉 94% 以上的日志) - 将
pod从索引中降级 → 在典型的 K8s 部署中,流数量可减少约 5 倍(先迁移选择器) - 尽可能将正则表达式行筛选器(
|~)替换为精确筛选器(|=) - 保留
service_name(及同类标签);如果值是 UUID 或临时值,则规范化为稳定标识——不要删除该键
Alloy / Agent 配置模式
规范化日志级别
loki.process "normalize_level" {
forward_to = [...]
stage.replace { source = "level"; expression = "(?i)I(nfo)?"; replace = "info" }
stage.replace { source = "level"; expression = "(?i)W(arn(ing)?)?"; replace = "warn" }
stage.replace { source = "level"; expression = "(?i)E(rr(or)?)?"; replace = "error" }
stage.replace { source = "level"; expression = "(?i)D(ebug?)?"; replace = "debug" }
stage.labels { values = { level = "" } }
}
有条件地提取元标签
// Only extract when the relevant field is present — avoids unnecessary cardinality
loki.process "conditional_extraction" {
forward_to = [...]
stage.match {
selector = "{app=\"loki\"} |= \"component\""
stage.logfmt { mapping = { "component" = "" } }
stage.labels { values = { component = "" } }
}
}
强制使用获批的标签集(始终作为最后一个阶段)
存在时,始终包含允许列表中的关联标签——切勿从 label_keep 中省略 service_name、deployment_environment 或 job(protected-labels.md):
loki.process "enforce_labels" {
forward_to = [loki.write.default.receiver]
// ... other stages ...
stage.label_keep {
values = [
"service_name", "deployment_environment", "job",
"env", "cluster", "level", "namespace", "workload", "container",
]
}
}
软性强制(为缺失标签填入“unknown”)
stage.template {
source = "team"
template = "{{ if .Value }}{{ .Value }}{{ else }}unknown{{ end }}"
}
stage.labels { values = { team = "" } }
日志行优化
按字节减少内容(时间戳、ANSI、空 JSON 字段),以节省场景 C 的开销——参见 references/log-line-optimization.md。
安全性与 LBAC
Grafana Enterprise Logs(GEL)支持基于标签的访问控制(LBAC)。任何标签都可用作访问控制选择器。
最适合用于 LBAC 的标签:
classification— 数据敏感级别(public、restricted、confidential、top-secret)source— 控制哪些团队可以查看哪些日志来源team/squad— 基于所有权的访问控制env— 环境级限制
owner=sysadmins 或 category=database 这类静态聚合标签尤其有效:一个标签值即可控制对多个日志文件的访问,而无需维护冗长的文件名或流允许列表。
80/20 规则
最具影响力的改进几乎总是来自以下四项调整:
- 将
pod从索引中移出(改为结构化元数据)——这是减少 K8s 数据流量最有效的方法;先迁移选择器 - 将
level添加为标签,并始终在查询中指定它——搜索错误时,可将扫描数据量减少 94% 以上 - 规范化标签值——消除大小写不一致产生的虚假重复数据流;对于
service_name,应固定 UUID/临时值(绝不要移除该键) - 在 K8s 中规范化或将
filename移出索引——变化频繁的路径会显著增加数据流数量
请优先处理这些问题。绝不要通过移除 service_name、deployment_environment 或 job 来“修复”基数问题。
应避免的标签——速查
| 标签 | 原因 | 替代方案 |
|---|---|---|
pod | 临时值,基数高 | 移出索引:使用 workload 标签,并将 pod 存为结构化元数据(迁移选择器) |
user_id | 无上限——绝不适合作为索引标签 | 仅保留在日志内容中 |
request_id / trace_id | 无上限——绝不适合作为索引标签 | 结构化元数据 |
filename(原始 K8s 路径) | 包含 Pod UID | 检查选择器后进行规范化或移出索引 |
未规范化的 level | INFO/info/Info = 3 个数据流 | 在采集时规范化 |
UUID/临时 service_name 值 | 增加数据流数量;仍需保留该键 | 保留该键;将值映射到稳定的服务标识 |
| 任何名称动态变化的标签键 | 无法限定取值范围 | 使用取值范围有限的固定键 |
绝不要移除: service_name、deployment_environment、job——请参阅 references/protected-labels.md。
成本影响分析
仅靠标签规范无法减少计费摄取字节数(直接成本影响为 $0)。启用 stage.drop/清理日志行才会节省数据量。编写报告部分时,请加载 references/cost-impact.md:采用其中的场景卡片格式,引用标量指标(可选附上简短的面板 ID/PromQL),并且绝不要将仅供 Agent 使用的参考表或面板 JSON 粘贴到客户报告中。