关于 distributed-logging
用于规划分布式日志管道的设计技能,面向需要汇集多个进程日志的系统设计者。内容涵盖采集、缓冲、索引、保留策略及故障处理。
主要功能
- 01日志采集与传输方案
- 02索引和存储权衡
- 03保留与分层存储建议
- 04背压与过载处理
- 05结构化事件和追踪关联
适用场景
- 01汇集多个服务的日志
- 02关联跨服务请求
- 03规划合规所需的日志保留
- 04应对日志突增且避免阻塞应用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill distributed-logging安装后在 Claude Code 中输入/distributed-logging确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/deep-dive.mdreferences/providers/aws.mdazure.mdgcp.mdgeneric.md详细介绍
分布式日志
将数千个进程的日志汇集到一个可搜索的位置,既要快到足以排查正在发生的故障,又要便宜到可以保留数月。设计不当是典型的“忽视故障”问题:日志管道本身就是一个分布式系统,而它恰恰会在你最需要它的时候因流量激增而不堪重负;天真的设计要么丢失证据,要么拖垮它所监控的应用。
适用场景
多个进程会产生日志,且有人需要统一搜索;故障排查需要跨服务关联同一个请求;日志量已经大到无法在单台机器上用 grep;或者合规要求必须保留日志。该管道提供集中搜索、跨服务关联,以及不依赖任何单台主机的持久记录。
不适用场景
单台主机上的单个服务,使用 journald 和日志轮转就够了——完整的管道纯属运维负担(YAGNI)。数值型时间序列问题(“p99 延迟是多少”“错误率是否上升”)属于指标,而非日志扫描——这是 observability 要解决的问题;日志回答的是“这个请求究竟发生了什么”。在数据尚未证明日志量值得承担相应成本之前,不要以全量发送所有调试日志;先进行采样。
先澄清
- 日志量和峰值——每秒行数和每秒字节数,平均值与峰值(→
back-of-the-envelope)。据此确定每个阶段的容量。 - 结构化日志还是自由文本——生产者现在能否输出 JSON,还是需要解析遗留文本?
- 查询延迟要求——需要在数秒内交互式搜索(热索引),还是偶尔进行取证/审计读取(冷归档)?
- 保留期限与合规要求——热数据保留多久、冷数据保留多久?是否需要法律保全或 PII 脱敏?
- 容忍丢失的程度——过载时是否可以丢弃/采样日志,还是每一行都是证据(审计/财务)?
方案
采集(主机上的 Agent)
- **Sidecar/节点 Agent(Fluentd、Fluent Bit、Vector、Filebeat):**读取文件或标准输出,添加元数据并发送出去。适用于应用将日志写入文件/标准输出,且你希望不改动应用代码的场景——这是默认选择。
- **直接写入总线的 SDK:**应用直接向传输层写入结构化事件。适用于你能控制代码且希望精确控制结构的场景,但会接受更紧密的耦合。
传输/缓冲(减震器)
- **Agent 侧缓冲 + 直接发送到索引器:**最简单;Agent 使用磁盘缓冲并重试。适用于中低日志量且只有一个使用方的场景。
- **持久化日志总线(
messaging-streaming,例如 Kafka):**生产者将数据写入分区总线;索引器按自身速度消费。适用于日志量大,或多个目标(搜索、归档、分析)需要读取同一数据流的场景。
索引/存储(搜索后端)
- **全文索引(Elasticsearch/OpenSearch——ELK/EFK 中的“E”):**查询能力丰富,但 RAM/磁盘开销大。适用于交互式字段搜索很重要的场景。
- **按标签索引的存储(Loki):**只索引标签,以压缩形式存储日志正文;成本低得多,支持类似 grep 的查询。适用于日志量大,且主要按服务/标签筛选后再扫描的场景。
保留/分层
- **热索引 → 冷对象存储(
blob-store):**将数天的可搜索数据保留在热存储中,再把较早的数据转存为压缩对象。只要保留期限超过热存储的经济可行范围,就应采用这种方案(几乎总是如此)。
权衡
| 选项 | 解决的问题 | 带来的问题 | 何时更改 |
|---|---|---|---|
| Node Agent(Fluent Bit/Vector) | 无需修改应用;集中管理元数据和路由 | 每台主机多运行一个守护进程;解析消耗 CPU;Agent 可能滞后 | 需要精确结构 → 从应用发出结构化事件 |
| 直接接入总线的 SDK | 结构化事件清晰,无需解析文件 | 应用与传输方式耦合;总线宕机时应用会阻塞或丢失日志 | 耦合/可用性造成影响 → 恢复为 Agent + 本地缓冲区 |
| Agent 缓冲区 → 直接写入索引器 | 组件最少 | 索引器背压会影响生产者;无法重放;只有一个接收端 | 流量激增或需要多个接收端 → 添加日志总线 |
| 持久化日志总线(Kafka) | 吸收流量峰值、解耦、重放、扇出 | 需要额外运行一套系统;仅保证分区内顺序;有成本 | 流量较低且只有一个接收端 → 移除总线 |
| 全文索引(ES/OpenSearch) | 快速搜索丰富字段 | 消耗大量 RAM/磁盘;映射膨胀;规模扩大后成本高 | 成本成为主要问题且查询按标签筛选 → 使用 Loki |
| 标签索引(Loki) | 高流量下存储成本低 | 全文搜索能力弱;基数高时扫描缓慢 | 确实需要任意字段搜索 → 使用全文索引 |
| 热索引 + 冷归档 | 长期保留成本低 | 冷数据读取缓慢,且需要手动恢复 | 必须快速读取旧数据进行取证 → 扩大热数据保留窗口 |
压力下的行为
该管道的故障模式在于,故障会引发日志流量激增——服务中断会产生大量错误和堆栈跟踪,恰逢管道最繁忙的时候,因此管道必须能够降级,而不是放大故障。
- 生产者背压:如果索引器变慢,而 Agent 同步发送日志,日志调用可能会阻塞应用。*缓解措施:*使用有界本地缓冲区,并在溢出时丢弃最新日志 / 采样,绝不阻塞请求路径。持久化总线可将积压日志从主机转移出去(背压 + DLQ 语义由
messaging-streaming负责)。 - **流量放大:**一次糟糕的部署若以 debug 级别记录日志,可能使流量增加 100 倍并撑爆索引。*缓解措施:*在 Agent 端按服务设置速率上限(速率限制由
resilience-failure负责)、动态采样,并针对每秒摄入字节数设置告警。 - **索引热分片 / 映射膨胀:**高基数字段(例如将原始用户 ID 用作索引字段)或某个过大的分片都可能拖垮集群。*缓解措施:*按时间创建索引、限制字段映射、规范标签使用。
- **顺序:**总线仅保证分区内顺序;合并后的多主机数据流会交错。*缓解措施:*查询时按事件时间戳排序;携带单调递增序列或跟踪 Span 顺序,不要依赖到达顺序。
- **证据丢失:**静默丢弃日志会掩盖正在调查的故障本身。*缓解措施:*统计并告警丢弃/采样率,让数据损失可见。
**监控:**每秒摄入字节数和行数、端到端发送延迟、总线消费者延迟、Agent 缓冲区占用率 + 丢弃率、索引队列/拒绝率、查询延迟。
如何应用
- 明确输入 — 确定日志量(平均值 + 峰值字节/秒)、结构化日志还是文本日志、查询延迟要求、保留期限和可容忍的丢失量(参见 先明确需求)。如果日志量极小且仅限单台主机,就此打住——使用主机本地日志即可(YAGNI)。
- 根据权衡表选择阶段 — 选择采集器,判断是否需要持久化总线(取决于日志量、峰值和接收端数量),根据查询需求选择索引后端,并确定热存储窗口和冷存储层。
- 设置关键参数 — 强制使用结构化架构,并确保每行都有 关联/跟踪 ID;设置 Agent 缓冲区大小和溢出策略、采样率、索引滚动周期(按时间)和分片大小,以及保留和分层策略。
- 对方案进行压力测试 — 逐项检查 压力下的行为(生产者背压、调试日志洪流式部署、热点分片、顺序、静默丢弃),确认管道会通过采样/缓冲来降级,而不会阻塞应用。
- 用数字估算规模 — 用每秒行数 × 每行字节数 × 峰值系数 × 保留期限,计算热索引 GB 和冷归档 GB/月;确认总线分区数量和索引器集群能够应对峰值摄取量(→ 重要指标)。
- 选择提供方 — 默认采用通用方案;只有用户指定了云服务时,才打开对应的提供方文件(参见 选择提供方)。
- 在回答中展示权衡 — 提供一张
Decision | Solves | Worsens | Change it when…表格,每项已选方案占一行(例如所选选项、其存储、其故障策略);最后一列填写具体触发条件(数字或可观测信号)。然后说明最先出故障的环节。
注意事项
应该
- 输出带有关联/跟踪 ID 的 结构化日志(JSON),在边缘处添加该 ID 并一路传递,使一次请求只需一次查询。
- 在生产者和索引器之间放置一个有界缓冲区,溢出时丢弃或采样,而不是阻塞应用。
- 当日志量出现峰值或有多个接收端读取流时,使用持久化日志总线。
- 积极分层:搜索使用较短的热存储窗口,长期保留使用压缩冷归档。
- 监测摄取速率、传输延迟和丢弃率,并针对这些指标设置告警——看不见的数据丢失才是陷阱。
不要
- 不要让日志调用阻塞或导致请求路径崩溃;默认情况下,日志记录应尽力而为。
- 不要为高基数自由格式字段建立索引——这会让集群负担暴增;标签应保持基数受限。
- 不要依赖到达顺序判断因果关系;应按事件时间/序列排序(→
sequencer)。 - 不要在此重复讲解指标/告警,也不要在日志中设置 SLO——这些属于
observability。 - 不要为了避免读取冷数据而永远保留热数据;热存储窗口应与调试窗口匹配。
重要指标
根据日志量为管道估算规模:每秒行数 × 每行字节数可得摄取字节数/秒;每行几 KB、每秒数万行时,摄取量就已达到每秒数百 MB、每天数 TB。热索引存储 ≈ 每日字节数 × 热存储天数 × (1 + 副本 + 开销);冷归档 ≈ 每日字节数 × 保留天数,压缩后约为原大小的 1/5–1/10。针对事故期间的日志洪流应用峰值系数。不要在此重复延迟/QPS/存储的经验法则——请从 back-of-the-envelope 中引用。
接口示意
日志行是一种契约:它是结构化事件,不是字符串。最少需要以下字段:
{ "ts": "2026-05-29T12:00:00.123Z", "level": "ERROR", "service": "checkout",
"host": "pod-7", "trace_id": "abc123", "span_id": "f9", "msg": "charge failed",
"user_id": "u42", "err": "timeout", "latency_ms": 812 }
ts 是事件时间(查询时的排序键);trace_id/span_id 用于跨服务关联;level 和 service 是基数受限的索引标签;高基数字段(user_id)应保留为可搜索的正文内容,而不是索引键。总线分区键通常是 service 或 trace_id,以便将同一请求的日志行排在一起。
选择提供方
默认采用上述通用方案。如果用户指定了云服务,请阅读 references/providers/<provider>.md,了解托管服务映射、配额/限制和提供方特有的权衡。如果该提供方没有对应文件,则采用通用方案。
图示
要可视化管道(生产者 → Agent → 总线 → 索引器/搜索 + 归档接收端)
或溢出/降级路径,请使用插件内的 architecture-diagram 技能 —
将持久化总线画成生产者与接收端之间的缓冲区,将冷归档接收端
使用 blob-store 颜色,并将溢出时丢弃的路径画成虚线箭头。
相关构建模块
observability— 所属概念位于 三支柱视图中:指标、跟踪、告警以及 SLO/SLI 都在那里讲解;日志是其中一支柱,而要针对什么内容发出告警属于它的职责,不属于此技能。messaging-streaming— 依赖它提供持久化日志总线:投递保证、排序、背压和 DLQ 语义由它负责;此技能仅将总线用作传输通道。blob-store— 为其提供冷日志归档:压缩冷对象的持久性、分层和生命周期都由它负责。sequencer— 当跨主机排序很重要时,与此配合使用;单调 ID 和时钟偏差处理由它负责。system-design— 所属概念位于编排器中:推理循环、权衡方法以及十种故障模式。为更广泛的设计提供支持。
参考资料
references/deep-dive.md— 缓冲和背压机制、采样策略、结构化日志 + 关联 ID 传播、索引生命周期/滚动、排序和冷存储分层。详细设计管道时请阅读。references/providers/{generic,aws,azure,gcp}.md— 各环境的服务映射、限制和注意事项。