日志支柱 · 最老也最被低估的那根
日志是最古老、最好上手、也最容易失控的支柱。这一章讲结构化日志为什么是分水岭、Loki「只索引标签」的思路为什么和 Prometheus 同源、日志分级的实用判据,以及日志与其他两根支柱的关系。
日志支柱 · 最老也最被低估的那根
三大支柱的最后一根:日志。
它最古老(printf 就是日志)、最好上手(谁都会写)、但也最容易失控——很多团队的日志账单比业务服务器还贵,而排障时还是找不到想要的。
分水岭:结构化
**日志工程只有一条分水岭:它是给人读的字符串,还是给机器查的数据?
❌ 非结构化(传统)
2026-07-15 10:23:45 ERROR Failed to process order 12345 for user 678: timeout after 30s
✅ 结构化(JSON)
{"ts":"2026-07-15T10:23:45Z", "level":"error", "msg":"failed to process order",
"order_id":"12345", "user_id":"678", "error":"timeout", "duration_ms":30000,
"trace_id":"abc123", "service":"order-api"}区别不是“好看”——是能不能查:
| 你想问 | 非结构化 | 结构化 |
|---|---|---|
| “所有超时的错误” | 正则捞文本,脆弱(改一个字就废) | error="timeout",精确 |
| “user 678 的所有操作” | grep 678 → 会误中 order_id、金额、任何含 678 的东西 | user_id="678",准确 |
| “P99 处理时长” | 做不到(得从文本里抠数字) | duration_ms 直接算 |
| “跳到这条日志的 trace” | 做不到 | trace_id 一键跳 |
非结构化日志的根本问题:它把“数据”编码成了“散文”,而散文没法可靠地反解析。 你写日志时脑子里的那个结构(订单号、用户、错误类型),在拼成字符串的那一刻就丢失了——之后所有人都要靠正则去猜它。
结构化是这一章唯一的“必须做”。 其他都是优化,这个是及格线。
Loki 的思路:只索引标签
传统日志系统(如 ES)的做法:给日志内容建全文索引。强大,但极贵**——索引往往比原始数据还大。**
Loki 反过来:只索引少量标签,内容完全不索引。
{service="order-api", level="error"} ← 只索引这些(和 Prometheus 一模一样的标签!)
|= "timeout" ← 内容靠"暴力扫描"过滤这个取舍很硬核:
- 写入极便宜 —— 不用建全文索引,日志压缩后直接存对象存储。成本可能是 ES 的十分之一。
- 按标签查极快 —— 和 Prometheus 的倒排索引同源。
- 全文搜索靠扫 —— 但因为标签已经把范围缩得很小了(比如只扫 order-api 服务、error 级别、最近 1 小时),扫的数据量其实不大。
关键洞见:Loki 赌的是“你几乎总是先用标签缩小范围,再在小范围里搜内容”。 这个假设在排障场景下基本成立——你从来不会“在全公司所有日志里搜一个词”,你总是从指标或 trace 跳过来的,已经知道是哪个服务、哪个时间段。
它的标签体系和 Prometheus 刻意一致,所以你可以用几乎一样的标签在两个系统间跳转。“让日志用指标的标签模型”是 Loki 最聪明的一步。
代价也很实在:如果你真的需要“全库全文检索”(比如安全审计、合规查询),Loki 不合适——那是 ES 的场子。又一次:用对工具。
注意:Loki 的标签同样有基数问题**!** 和 Prometheus 一模一样:把 user_id 放进 Loki 的标签,一样会炸(它的索引也是按标签组合建的)。
正确做法:user_id 放进日志内容(JSON 字段),用 | json | user_id="678" 过滤——内容里可以有任意高基数的东西,标签里不行。
这是 Loki 最常见的误用:以为“日志嘛,随便打标签”,结果把 Loki 用成了一个更慢的 ES。基数思维在这里同样适用。
日志分级:一个实用判据
DEBUG/INFO/WARN/ERROR 人人都会写,但标准极其混乱**。给一个可执行的判据:**
| 级别 | 判据 | 生产开吗 |
|---|---|---|
| ERROR | 需要人看(有人得做点什么) | ✅ |
| WARN | 可能有问题,但系统自己处理了(重试成功了) | ✅ |
| INFO | 描述正常的重要事件(服务启动、配置加载、发布) | ✅(但要克制) |
| DEBUG | 排查时才要的细节 | ❌(按需临时开) |
最有用的一条:ERROR 的判据是“需要人看”,不是“发生了异常”。
很多代码里,一个正常的业务拒绝(用户输错密码)被打成 ERROR——于是 error 日志里 99% 是噪音,真正的 ERROR 淹没其中。这和告警疲劳是同一个病:信噪比是公共资源。
判据:如果这条 ERROR 一天出现 10 万次而没人做任何事,它就不是 ERROR。 降成 INFO,或者干脆做成指标(login_failures_total 这种东西天生就该是 counter,不是日志)。
这条推论很重要:很多人用日志记录“发生了多少次 X”——那是指标的活。 日志是给“需要看细节的个案”用的,不是给“数数”用的。用日志数数,你会同时付出日志的高成本和聚合的麻烦。
日志和另外两根的关系
回到第 0 部分的排障流:指标发现 → 追踪定位哪一跳 → 日志看清那一跳的细节。
日志是“最后一公里”——当你已经知道“是订单服务、10:23、那个慢请求”时,日志告诉你它当时到底怎么了(哪个 SQL、什么错误、什么参数)。
所以日志的价值高度依赖于“能被精确定位”:
- 有
trace_id→ 从 trace 一键跳过来,只看这一个请求的日志。这是日志最高效的用法。 - 没有 trace_id → 你只能“按时间 + 服务”捞,在几万条里翻。效率差一个数量级。
第 0 部分提过的“宽事件”批评在这里显出意义: 如果你的日志是结构化 + 高维度 + 带 trace_id 的,它其实已经很接近“宽事件”了——指标可以从它聚合出来、trace 可以从它关联。三支柱的边界,在实践中本来就是模糊的。
SpanEvent 是另一条路 —— 把事件直接挂在 span 上,天然带着完整的 trace 上下文,不用额外做关联。异常堆栈尤其适合这么记。
小结与下一部分
日志的唯一分水岭是结构化——非结构化把“数据”编码成“散文”,而写日志时脑子里的结构在拼成字符串那刻就丢了,之后所有人只能靠正则猜;结构化是及格线,不是优化。Loki 只索引标签、内容不索引:写入极便宜、按标签查极快(与 Prometheus 倒排索引同源)、全文靠扫但因标签已缩小范围而可行——它赌的是“你总是先用标签缩范围”(排障时你总是从指标或 trace 跳过来的,这假设成立);但 Loki 的标签同样有基数问题——user_id 该放内容不放标签,这是最常见的误用。分级判据:ERROR 是“需要人看”,不是“发生了异常”——一天 10 万次没人管的 ERROR 就不是 ERROR;而“数数”是指标的活,不是日志的(用日志数数会同时付出高成本和聚合的麻烦)。日志是排障的最后一公里,其价值高度依赖能否被精确定位——有 trace_id 一键跳,没有就只能翻几万条;结构化+高维+带 trace_id 的日志已接近“宽事件”,三支柱的边界本就模糊。
数据都齐了、也串起来了。最后一个问题:怎么把它变成人能看懂的东西? 👉 第 8 部分 · Grafana。
参考资料
- [1] Grafana Loki 官方文档:Labels、LogQL、Best practices(标签基数)。
- [2] OTel 官方文档:Logs、Logs Data Model;OTel Collector
pdata/plog。 - [3] 关联三大支柱、exemplar 与关联、基数。