# 日志支柱 · 最老也最被低估的那根

> 日志是最古老、最好上手、也最容易失控的支柱。这一章讲结构化日志为什么是分水岭、Loki「只索引标签」的思路为什么和 Prometheus 同源、日志分级的实用判据，以及日志与其他两根支柱的关系。

- 作者：David（道雾轩）
- 专栏：指标采集与观测（https://daiw.org/manual/observability.md）
- 最后更新：2026-07-15
- 原文：https://daiw.org/manual/observability/logs-pillar
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 日志支柱 · 最老也最被低估的那根

[三大支柱](https://daiw.org/manual/observability/three-pillars)的最后一根：**日志**。

它最古老（`printf` 就是日志）、最好上手（谁都会写）、但也**最容易失控**——很多团队的日志账单比业务服务器还贵，而排障时还是找不到想要的。

## 分水岭：结构化

<Callout type="warn">
  **日志工程只有一条分水岭：**它是给人读的字符串，还是给机器查的数据？**

  ```
  ❌ 非结构化(传统)
  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` 一键跳](https://daiw.org/manual/observability/exemplars) |

  **非结构化日志的根本问题：它把“数据”编码成了“散文”，而散文没法可靠地反解析。** 你写日志时脑子里的那个结构（订单号、用户、错误类型），**在拼成字符串的那一刻就丢失了**——之后所有人都要靠正则去猜它。

  **结构化是这一章唯一的“必须做”。** 其他都是优化，这个是及格线。
</Callout>

## Loki 的思路：只索引标签

<Callout type="info">
  **传统日志系统（如 ES）的做法：**给日志内容建全文索引**。强大，但**极贵**——索引往往比原始数据还大。**

  **Loki 反过来：**只索引少量标签，内容完全不索引**。**

  ```
  {service="order-api", level="error"}   ← 只索引这些(和 Prometheus 一模一样的标签!)
  |= "timeout"                            ← 内容靠"暴力扫描"过滤
  ```

  **这个取舍很硬核：**
  - **写入极便宜** —— 不用建全文索引，日志压缩后直接存对象存储。**成本可能是 ES 的十分之一。**
  - **按标签查极快** —— [和 Prometheus 的倒排索引同源](https://daiw.org/manual/observability/index-format)。
  - **全文搜索靠扫** —— 但因为**标签已经把范围缩得很小了**（比如只扫 order-api 服务、error 级别、最近 1 小时），**扫的数据量其实不大**。

  **关键洞见：Loki 赌的是“你几乎总是先用标签缩小范围，再在小范围里搜内容”。** 这个假设在**排障场景**下基本成立——你从来不会“在全公司所有日志里搜一个词”，你总是[从指标或 trace 跳过来的](https://daiw.org/manual/observability/exemplars)，**已经知道是哪个服务、哪个时间段**。

  **它的标签体系和 Prometheus 刻意一致**，所以你可以用几乎一样的标签在两个系统间跳转。**“[让日志用指标的标签模型](https://daiw.org/manual/observability/data-model)”是 Loki 最聪明的一步。**

  **代价也很实在**：如果你真的需要“全库全文检索”（比如安全审计、合规查询），Loki **不合适**——那是 ES 的场子。**又一次：[用对工具](https://daiw.org/manual/observability/three-pillars)。**
</Callout>

<Callout type="warn">
  **注意：Loki 的标签**同样有基数问题**！** [和 Prometheus 一模一样](https://daiw.org/manual/observability/defining-metrics)：把 `user_id` 放进 Loki 的**标签**，一样会炸（它的索引也是按标签组合建的）。

  **正确做法**：`user_id` 放进**日志内容**（JSON 字段），用 `| json | user_id="678"` 过滤——**内容里可以有任意高基数的东西，标签里不行**。

  **这是 Loki 最常见的误用**：以为“日志嘛，随便打标签”，结果把 Loki 用成了一个更慢的 ES。**[基数思维](https://daiw.org/manual/observability/defining-metrics)在这里同样适用。**
</Callout>

## 日志分级：一个实用判据

<Callout type="info">
  **`DEBUG`/`INFO`/`WARN`/`ERROR` 人人都会写，但**标准极其混乱**。给一个可执行的判据：**

  | 级别 | 判据 | 生产开吗 |
  | --- | --- | --- |
  | **ERROR** | **需要人看**（有人得做点什么） | ✅ |
  | **WARN** | **可能有问题，但系统自己处理了**（重试成功了） | ✅ |
  | **INFO** | **描述正常的重要事件**（服务启动、配置加载、[发布](https://daiw.org/manual/observability/slo-burn-rate)） | ✅（但要克制） |
  | **DEBUG** | **排查时才要的细节** | ❌（按需临时开） |

  **最有用的一条：`ERROR` 的判据是“需要人看”，不是“发生了异常”。**

  很多代码里，一个正常的业务拒绝（用户输错密码）被打成 `ERROR`——于是 error 日志里 99% 是噪音，**真正的 ERROR 淹没其中**。[这和告警疲劳是同一个病](https://daiw.org/manual/observability/alerting-philosophy)：**信噪比是公共资源**。

  **判据：如果这条 ERROR 一天出现 10 万次而没人做任何事，它就不是 ERROR。** 降成 INFO，或者干脆[做成指标](https://daiw.org/manual/observability/metric-types)（`login_failures_total` 这种东西**天生就该是 counter，不是日志**）。

  **这条推论很重要：很多人用日志记录“发生了多少次 X”——那是[指标的活](https://daiw.org/manual/observability/three-pillars)。** [日志是给“需要看细节的个案”用的，不是给“数数”用的](https://daiw.org/manual/observability/three-pillars)。用日志数数，你会同时付出[日志的高成本](https://daiw.org/manual/observability/three-pillars)和[聚合的麻烦](https://daiw.org/manual/observability/promql-aggregation)。
</Callout>

## 日志和另外两根的关系

<Callout type="info">
  **回到[第 0 部分的排障流](https://daiw.org/manual/observability/three-pillars)：[指标发现](https://daiw.org/manual/observability/red-method) → [追踪定位哪一跳](https://daiw.org/manual/observability/traces-basics) → **日志看清那一跳的细节**。**

  **日志是“最后一公里”**——当你已经知道“是订单服务、10:23、那个慢请求”时，日志告诉你**它当时到底怎么了**（哪个 SQL、什么错误、什么参数）。

  **所以日志的价值高度依赖于“能被精确定位”**：
  - **有 [`trace_id`](https://daiw.org/manual/observability/exemplars)** → [从 trace 一键跳过来](https://daiw.org/manual/observability/exemplars)，只看这一个请求的日志。**这是日志最高效的用法。**
  - **没有 trace_id** → 你只能“按时间 + 服务”捞，在几万条里翻。**效率差一个数量级。**

  **[第 0 部分提过的“宽事件”批评](https://daiw.org/manual/observability/three-pillars)在这里显出意义：** 如果你的日志是**结构化 + 高维度 + 带 trace_id** 的，它其实已经很接近“宽事件”了——**指标可以从它聚合出来、trace 可以从它关联**。三支柱的边界，在实践中本来就是模糊的。

  **[`SpanEvent` 是另一条路](https://daiw.org/manual/observability/traces-basics)** —— 把事件直接挂在 span 上，**天然带着完整的 trace 上下文**，不用额外做关联。异常堆栈尤其适合这么记。
</Callout>

## 小结与下一部分

**日志的唯一分水岭是[结构化](https://daiw.org/manual/observability/logs-pillar)**——[非结构化把“数据”编码成“散文”，而写日志时脑子里的结构在拼成字符串那刻就丢了，之后所有人只能靠正则猜](https://daiw.org/manual/observability/logs-pillar)；结构化是**及格线**，不是优化。**Loki 只索引标签、内容不索引**：[写入极便宜、按标签查极快（与 Prometheus 倒排索引同源）、全文靠扫但因标签已缩小范围而可行](https://daiw.org/manual/observability/index-format)——**它赌的是“你总是先用标签缩范围”**（排障时你[总是从指标或 trace 跳过来的](https://daiw.org/manual/observability/exemplars)，这假设成立）；**但 Loki 的标签同样有基数问题**——[`user_id` 该放内容不放标签，这是最常见的误用](https://daiw.org/manual/observability/defining-metrics)。**分级判据：`ERROR` 是“需要人看”，不是“发生了异常”**——[一天 10 万次没人管的 ERROR 就不是 ERROR；而“数数”是指标的活，不是日志的](https://daiw.org/manual/observability/alerting-philosophy)（用日志数数会同时付出高成本和聚合的麻烦）。日志是**排障的最后一公里**，其价值[高度依赖能否被精确定位](https://daiw.org/manual/observability/exemplars)——**有 `trace_id` 一键跳，没有就只能翻几万条**；[结构化+高维+带 trace_id 的日志已接近“宽事件”](https://daiw.org/manual/observability/three-pillars)，三支柱的边界本就模糊。

数据都齐了、也串起来了。最后一个问题：**怎么把它变成人能看懂的东西？** 👉 第 8 部分 · Grafana。

## 参考资料

- [1] Grafana Loki 官方文档：Labels、LogQL、Best practices（标签基数）。
- [2] OTel 官方文档：Logs、Logs Data Model;OTel Collector `pdata/plog`。
- [3] 关联[三大支柱](https://daiw.org/manual/observability/three-pillars)、[exemplar 与关联](https://daiw.org/manual/observability/exemplars)、[基数](https://daiw.org/manual/observability/defining-metrics)。
