Grafana Loki 日志聚合体系 从采集到查询的落地清单
Loki 的设计哲学是"只索引标签,不索引日志全文",这是它比 ELK 省一到两个数量级成本的根因,也是它使用门槛所在:标签设计得好,查询飞快;标签设计得糟,要么时间线爆炸让 Ingester 崩,要么查不出来。本文给一套从采集端到查询端的落地清单。
一、标签基数是第一性问题
Loki 每个唯一标签组合就是一条时间线(stream)。基数爆炸的元凶很明确:
禁止作为标签:trace_id、user_id、request_id、原始 URL 路径、异常堆栈、任何 UUID。
推荐标签:cluster、namespace、app、pod(受控数量)、level、env、region。
# Alloy / Promtail relabel:把高基数字段剔除,但保留在日志正文里
- labeldrop:
- trace_id
- request_id
- user_id
- relabel:
target: level
regex: "(INFO|WARN|ERROR|DEBUG)"
action: replace
replacement: "$1"
高价值字段仍要能查——它们以结构化日志 JSON 字段的形式存在于日志行内,用 LogQL 的 | json | 在查询时解析,索引里不留痕。
健康指标:单个租户活跃时间线数、loki_ingester_num_streams_active。生产上把总量控制在几万条以内,出现陡增立刻查是哪个标签炸了。
二、采集端:结构化是前提
// Go 侧统一输出带 trace_id 的 JSON,字段不进标签
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
})))
slog.Info("order created",
"order_no", orderNo,
"trace_id", traceID,
"amount", amount,
"latency_ms", cost,
)
Promtail/Alloy 侧对 JSON 日志抽取常用字段做临时标签再 drop:
- match:
selector: '{app="order-svc"}'
stages:
- json:
expressions:
lvl: level
msg: msg
- labels:
lvl:
- timestamp:
source: time
format: RFC3339Nano
三、LogQL 的四类常用查询
日志筛选与计数:
{app="order-svc", env="prod"} | json | level="ERROR" | order_no!=""
性能指标直方图:
histogram_quantile(0.99,
sum by (le) (rate({app="order-svc"} | json | unwrap latency_ms [5m]))
)
错误率随时间变化:
sum by (app) (rate({env="prod"} |~ "(?i)error|panic" [1m]))
/
sum by (app) (rate({env="prod"} [1m]))
trace 关联:trace_id 在正文里,用变量在 Grafana 面板联动跳 Tempo,而不是把 trace_id 塞进标签。
三个高频语法坑:正则一律用 |~;数字字段必须 | json | unwrap field 才能进聚合;rate() 的时间窗口不要小于抓取间隔的四倍。
四、存储与保留策略
Loki 按 chunk 对象存储 + 索引。生产推荐对象存储,冷热分层是成本关键。
limits_config:
retention_period: 30d
enforce_metric_name: false
max_query_series: 5000
max_query_parallelism: 32
split_queries_by_interval: 24h
volume_enabled: true
compactor:
delete_request_store: filesystem
retention_enabled: true
storage_config:
aws:
s3: s3://loki-endpoint/okgoes-logs
s3forcepathstyle: true
要点:
split_queries_by_interval让长查询并行分段拉取,否则 30 天查询会直接超时;max_query_series是防自毁的关键,挡住误写的宽查询;- 合规删除需求要开
retention_enabled+ delete API。
五、容量估算模型
粗算公式:
日写入 = QPS × 平均日志字节数 × 86400
chunk 存储 ≈ 日写入 × 保留天数 × 0.15 # gzip + 标签开销后
Ingester 内存 ≈ 活跃流数 × 每个流缓冲(默认 4MB) × 副本数
一个 2000 QPS、平均 600B 日志、30 天保留的服务:日写入约 100GB,chunk 存储约 450GB。Ingester 若 6 万流 × 4MB × 2 副本 ≈ 480GB,需要拆 component 并把 replica 提到 3。
生产部署一定要拆微服务模式:read / write / gateway 分离,各自独立扩缩。读写耦合是 Ingester OOM 后被查询拖垮的直接原因。
六、告警与降成本实践
日志告警走 Loki ruler + Alertmanager(LogQL as Rule):
- alert: OrderPanicSpike
expr: |
sum by (namespace) (count_over_time({app="order-svc"} |~ "(?i)panic:" [2m])) > 5
for: 2m
labels: {severity: "critical"}
annotations:
summary: "order-svc 出现 panic 突增"
降成本三板斧:
- 采样:DEBUG/INFO 级按比例丢弃,用
sample_stats记录丢弃率; - 分层:7 天后降到低频存储、只保留错误日志长留;
- 入口治理:噪声日志在 SDK 层降噪,源头省比下游省便宜十倍。
再配一条 Grafana Logs 面板与 Trace 面板的双向跳转,故障定位路径就能从"翻机器日志"缩到"从告警点进日志再点进 Trace"。
小结
Loki 的收益全部押在标签设计上:低基数标签进索引、高价值字段留正文按需解析。落地清单按顺序做——先审标签基数,再上结构化日志,再配限流与分片查询,最后用 ruler 打通告警。存储上冷热分层与读写分离决定长期成本,治理上源头降噪与采样决定增长速度。这四步都做完,百万级 QPS 的日志体系也能控制在可接受的成本与查询延迟里。
评论(0)
登录 后参与评论。