Skip to content
Gains Summary
Main Navigation 首页 / Home
C++ 编程 / C++ Programming
系统与高性能 / Systems & Performance
Web 开发 / Web Development
人工智能 / Artificial Intelligence
工业软件 / Industrial Software
其他内容 / Other Topics
C++ 编程 / C++系统与性能 / SystemsWeb 开发 / Web人工智能 / AI工业软件 / Industrial

外观

Sidebar Navigation

← Web 开发 / Web Development

后端工程 / Backend Engineering

1. Backend 后端技术全景学习路线 / A Complete Backend Engineering Learning Path

2. 分布式系统——为什么单体时代过去了 / Distributed Systems Beyond the Monolith

3. 并发编程——为什么你的库存总是扣成负数 / Concurrency Control and Inventory Consistency

4. MySQL 优化——为什么你的查询总是那么慢 / MySQL Query and Storage Optimization

5. 架构模式——什么时候该用 CQRS / Architecture Patterns and When to Use CQRS

6. 系统设计——如何设计一个每秒 10 万订单的秒杀系统 / Designing a High-Throughput Flash-Sale System

7. 数据库工程化——在线表结构变更怎么不停服 / Database Engineering and Online Schema Changes

8. 可观测性——出了问题怎么快速定位 / Observability and Rapid Production Diagnosis

本页目录

可观测性——出了问题怎么快速定位 / Observability and Rapid Production Diagnosis ​

📅 创建时间:2026-05-08 🏷️ 标签:#结构化日志 #ELK #OpenTelemetry #告警收敛 #可观测性 📚 前置知识:[[00-backend-overview]] [[05-middleware]](链路追踪) 📚 相关知识:[[12-devops-basics]](监控基础)


场景:用户说"下单失败了",但你不知道哪一步错了 ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  用户投诉:下单失败了,但我不知道是哪里出了问题。         │
│                                                             │
│  你开始排查:                                            │
│  1. 看应用日志?分散在 10 台服务器上...                │
│  2. 问前端?前端说接口返回 500                         │
│  3. 问 DBA?DBA 说数据库正常                           │
│  4. 问运维?运维说网络正常                             │
│                                                             │
│  花了 2 小时,才发现是 Redis 连接池满了。               │
│                                                             │
│  如果有可观测性,应该在 Redis 满的那一刻就报警。         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

可观测性 = 日志 + 指标 + 链路追踪,三者缺一不可。


第1节:结构化日志——不是 console.log ​

问题:非结构化日志难以分析 ​

❌ 难以分析:
2026-05-08 10:00:00 ERROR [OrderService] order create failed, userId=123, error=timeout
2026-05-08 10:00:01 ERROR [OrderService] failed to create order for user 456: connection refused

✅ 易于分析:
{
  "timestamp": "2026-05-08T10:00:00Z",
  "level": "ERROR",
  "service": "order-service",
  "traceId": "abc123",
  "spanId": "def456",
  "event": "order_create_failed",
  "userId": 123,
  "error": "timeout",
  "duration": 5000,
  "metadata": {
    "productId": 789,
    "inventory": 0
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

结构化日志实现 ​

python
import logging
import json
import uuid

# 结构化日志格式
class StructuredFormatter(logging.Formatter):
    def format(self, record):
        log_entry = {
            "timestamp": self.formatTime(record),
            "level": record.levelname,
            "service": getattr(record, "service", "unknown"),
            "traceId": getattr(record, "traceId", uuid.uuid4().hex[:16]),
            "spanId": getattr(record, "spanId", ""),
            "logger": record.name,
            "message": record.getMessage(),
        }

        # 添加 extra 字段
        if hasattr(record, "extra"):
            log_entry["metadata"] = record.extra

        # 添加异常信息
        if record.exc_info:
            log_entry["exception"] = self.formatException(record.exc_info)

        return json.dumps(log_entry)


# 日志记录器
def get_logger(name, service):
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    handler = logging.StreamHandler()
    handler.setFormatter(StructuredFormatter())
    logger.addHandler(handler)

    # 注入服务名
    old_factory = logging.getLogRecordFactory()

    def record_factory(*args, **kwargs):
        record = old_factory(*args, **kwargs)
        record.service = service
        record.traceId = getattr(record, "traceId", uuid.uuid4().hex[:16])
        return record

    logging.setLogRecordFactory(record_factory)
    return logger


logger = get_logger(__name__, "order-service")

# 使用
logger.info("order created",
    extra={
        "orderId": 12345,
        "userId": 100,
        "productId": 789,
        "amount": 299.99
    })

logger.error("order creation failed",
    extra={
        "orderId": 12345,
        "error": "inventory insufficient",
        "availableStock": 0
    })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

日志等级规范 ​

┌─────────────────────────────────────────────────────────────┐
│              日志等级规范                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  DEBUG(调试):                                          │
│  → 开发时使用                                            │
│  → 打印变量值、函数入参、SQL 语句                       │
│  → 生产环境不输出                                        │
│                                                             │
│  INFO(信息):                                           │
│  → 业务关键节点                                          │
│  → "用户登录"、"订单创建"、"支付成功"              │
│  → 便于追踪业务流程                                      │
│                                                             │
│  WARN(警告):                                          │
│  → 异常情况,但不影响业务                             │
│  → "重试第 3 次"、"缓存 miss"                       │
│  → 警告多了说明系统不稳定                              │
│                                                             │
│  ERROR(错误):                                          │
│  → 影响单个请求,但不影响系统                       │
│  → "下单失败"、"支付超时"                          │
│  → 需要关注和修复                                        │
│                                                             │
│  FATAL(致命):                                          │
│  → 影响整个系统                                          │
│  → "数据库连接失败"、"内存不足"                     │
│  → 必须立即处理                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

第2节:ELK 栈——日志的存储和查询 ​

┌─────────────────────────────────────────────────────────────┐
│                 ELK 架构                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │                                                     │ │
│  │  应用服务(结构化 JSON 日志)                    │ │
│  │        │                                            │ │
│  │        ▼                                            │ │
│  │  ┌─────────┐                                       │ │
│  │  │Filebeat │ 轻量级日志收集                       │ │
│  │  └────┬────┘                                       │ │
│  │       │                                             │ │
│  │       ▼                                             │ │
│  │  ┌─────────┐                                       │ │
│  │  │ Logstash│ 解析、过滤、转换                    │ │
│  │  └────┬────┘                                       │ │
│  │       │                                             │ │
│  │       ▼                                             │ │
│  │  ┌─────────┐                                       │ │
│  │  │Elasticsearch│ 存储 + 全文搜索                  │ │
│  │  └────┬────┘                                       │ │
│  │       │                                             │ │
│  │       ▼                                             │ │
│  │  ┌─────────┐                                       │ │
│  │  │ Kibana │ 可视化 + 查询                        │ │
│  │  └─────────┘                                       │ │
│  │                                                     │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

Filebeat 配置 ​

yaml
# filebeat.yml
filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /var/log/order-service/*.log
    json.keys_under_root: true
    json.add_error_key: true
    json.message_key: message

processors:
  - add_host_metadata:
      when.not.contains.tags: forwarded
  - add_cloud_metadata: ~
  - add_docker_metadata: ~

output.elasticsearch:
  hosts: ["elasticsearch:9200"]
  index: "order-service-%{+yyyy.MM.dd}"

# 日志脱敏
processors:
  - redact:
      fields: ["user.password", "user.token", "user.phone"]
      pattern: '\b\d{11}\b'
      replacement: '****'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Kibana 查询 ​

┌─────────────────────────────────────────────────────────────┐
│              Kibana 常用查询                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  按服务查询:                                             │
│  service: "order-service"                                 │
│                                                             │
│  按 traceId 查询(链路追踪):                           │
│  traceId: "abc123def456"                                 │
│                                                             │
│  组合查询:                                              │
│  service: "order-service" AND level: "ERROR"             │
│  service: "order-service" AND message: "timeout"           │
│                                                             │
│  模糊查询:                                              │
│  message: "order*failed"                                  │
│                                                             │
│  聚合统计:                                              │
│  按 service 统计 ERROR 数量:                             │
│  GET /_search                                            │
│  {                                                       │
│    "aggs": {                                             │
│      "errors_by_service": {                              │
│        "terms": {"field": "service.keyword"},           │
│        "aggs": {                                        │
│          "errors": {                                     │
│            "filter": {"term": {"level": "ERROR"}}       │
│          }                                               │
│        }                                                 │
│      }                                                   │
│    }                                                     │
│  }                                                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

第3节:OpenTelemetry——统一埋点 ​

问题:每种语言都要单独埋点 ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  Java: Micrometer + Prometheus                          │
│  Python: OpenTelemetry                                  │
│  Go:    Zipkin                                          │
│  Node.js:Jaeger                                         │
│                                                             │
│  问题:跨语言链路追踪无法串联                         │
│  Java 服务 → Python 服务 → Go 服务                      │
│  → 每个服务的 traceId 不一致,无法追踪完整链路        │
│                                                             │
│  解决:OpenTelemetry(统一标准)                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14

OpenTelemetry 埋点 ​

python
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# 初始化
resource = Resource.create({
    "service.name": "order-service",
    "service.version": "1.0.0",
    "deployment.environment": "production"
})

provider = TracerProvider(resource=resource)
processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="otel-collector:4317"))
provider.add_span_processor(processor)
trace.set_tracer_provider(provider)

tracer = trace.get_tracer(__name__)

# 业务埋点
@app.route("/order", methods=["POST"])
def create_order():
    with tracer.start_as_current_span("create_order") as span:
        # 设置属性
        span.set_attribute("order.user_id", request.user_id)
        span.set_attribute("order.amount", request.amount)

        try:
            # 扣库存
            with tracer.start_as_current_span("deduct_inventory") as inventory_span:
                inventory_span.set_attribute("product.id", request.product_id)
                inventory_service.deduct(request.product_id)

            # 创建订单
            with tracer.start_as_current_span("save_order") as order_span:
                order_span.set_attribute("db.system", "mysql")
                order_span.set_attribute("db.statement", "INSERT INTO orders ...")
                order = order_service.create(request)

            span.set_status(trace.StatusCode.OK)
            return {"orderId": order.id}

        except InsufficientInventoryError as e:
            span.set_status(trace.StatusCode.ERROR, str(e))
            span.record_exception(e)
            raise
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

第4节:告警收敛——告警风暴 ​

问题:系统报警了,然后收到了 1000 条告警 ​

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  Redis 内存达到 90%。                                    │
│                                                             │
│  → 触发告警:"Redis 内存 90%,告警 1"              │
│  → 5 分钟后,又告警:"Redis 内存 90%,告警 2"       │
│  → 又过了 5 分钟...                                       │
│                                                             │
│  问题:                                                 │
│  同一个问题触发了 100 条告警                            │
│  真正的告警被淹没                                        │
│                                                             │
│  解决:告警收敛                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Alertmanager 告警收敛 ​

yaml
# Alertmanager 配置
global:
  resolve_timeout: 5m

route:
  group_by: ['alertname', 'severity']
  group_wait: 30s      # 等待 30 秒,聚合同组告警
  group_interval: 5m   # 同组告警的发送间隔
  repeat_interval: 4h   # 重复告警的发送间隔

  receiver: 'team-notifications'
  routes:
    # 严重告警:立即通知
    - match:
        severity: critical
      receiver: 'pagerduty'
      group_wait: 0s   # 立即告警

    # 警告告警:合并后通知
    - match:
        severity: warning
      receiver: 'slack'
      continue: true

    # Redis 告警:按服务聚合
    - match:
        service: redis
      receiver: 'redis-team'
      group_by: ['alertname', 'service']

# 静默规则
inhibit_rules:
  # 严重告警触发时,抑制同类的警告告警
  - source_match:
      severity: critical
    target_match:
      severity: warning
    equal: ['alertname', 'service']
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

告警 SOP ​

┌─────────────────────────────────────────────────────────────┐
│              告警处理 SOP                                 │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  收到告警:                                             │
│  Step 1:确认告警是否真实(排查监控)                    │
│  Step 2:确认影响范围(影响一个用户还是所有用户)        │
│  Step 3:临时止血(降级/限流/回滚)                     │
│  Step 4:定位根因                                        │
│  Step 5:彻底修复                                        │
│  Step 6:复盘记录                                        │
│                                                             │
│  告警质量标准:                                          │
│  ✅ 每个告警都有明确的处理动作                           │
│  ✅ 告警数量:每天 < 100 条                            │
│  ✅ 告警响应时间:P50 < 5 分钟                       │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

第5节:可观测性体系建立 ​

┌─────────────────────────────────────────────────────────────┐
│              可观测性三支柱                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 日志(Logs):出了什么问题                           │
│  → 结构化 JSON + traceId                                 │
│  → ELK 存储 + Kibana 查询                               │
│  → 按 traceId 串联所有日志                               │
│                                                             │
│  2. 指标(Metrics):系统健康吗                          │
│  → 黄金指标:Rate / Errors / Duration                   │
│  → Prometheus + Grafana                                 │
│  → 仪表盘:服务概览 / 依赖关系 / 趋势                   │
│                                                             │
│  3. 链路(Traces):哪一步慢了                         │
│  → OpenTelemetry 统一埋点                               │
│  → Jaeger / Zipkin / Tempo 可视化                      │
│  → 按 traceId 追踪完整调用链                           │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐ │
│  │  关联关系:                                        │ │
│  │  指标异常 → 找到慢的 trace → 查看日志定位问题   │ │
│  │  日志异常 → 找到 traceId → 查看链路找到根因   │ │
│  └─────────────────────────────────────────────────────┘ │
│                                                             │
└─────────────────────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

"AI 可查 vs 必须理解"清单 ​

AI 可查:
✅ ELK 各组件的具体配置
✅ OpenTelemetry SDK 的埋点 API
✅ Alertmanager 的配置语法

必须理解:
🔴 结构化日志的核心字段(timestamp / traceId / level / service)
🔴 ELK 三组件的职责(Filebeat / Logstash / Elasticsearch)
🔴 可观测性三支柱的关联(日志→追踪→指标)
🔴 告警收敛的原理和配置
1
2
3
4
5
6
7
8
9
10

学习状态:🟡 开始学习

最后更新于:

Pager
上一篇7. 数据库工程化——在线表结构变更怎么不停服 / Database Engineering and Online Schema Changes

持续记录,持续成长

Copyright © Tidenflow