懒人也能做到的代码可观测性实践指南

2026-08-25 38 预计阅读时间: 1 分钟
来源: cncf.io AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

开发者被要求把越来越多的问题“左移”:更早发现、更早验证、更早修复。现在,可观测性也开始进入开发阶段。它不只是线上平台团队的工作,而是代码作者在提交代码时就应该留下的诊断入口。

这里的重点不是给每个函数都加日志,而是让开发者能够回答几个实际问题:这段代码什么时候执行?输入和结果是什么?它为什么变慢或失败?出现问题时,是否能把一次请求串回完整的调用路径?

先观察关键路径,而不是给所有代码加日志

“可观测性左移”最容易被误解成“日志越多越好”。实际情况通常相反:无目的的日志会增加噪声、成本和隐私风险,反而让排查变慢。

可以从一条关键路径开始,例如:

  • 一个 HTTP 请求从入口到数据库的耗时;
  • 一次支付、订单或任务状态变化;
  • 一个经常失败的外部 API 调用;
  • 一段最近发生性能回退的代码。

针对这些路径,优先记录结构化事件:事件名称、耗时、结果、错误类型,以及用于串联同一次请求的标识。业务参数应经过筛选或脱敏,不要把完整请求体和访问令牌写入日志。

把诊断信息放进代码的自然位置

观察代码时,开发者通常需要三类信号:

  1. 日志:说明发生了什么,适合记录状态变化和明确错误。
  2. 指标:说明发生了多少次、花了多久,适合发现趋势和异常。
  3. 追踪:说明一次请求经过了哪些步骤,适合分析跨服务调用。

不必一开始就引入完整的平台。可以先用标准库在本地建立一个最小实践:为关键操作记录结构化日志和耗时。下面的 Python 示例可以直接运行,也可以改造成项目中的装饰器。

运行方式:保存为 observe_example.py,然后执行 python observe_example.py。示例使用标准库,不依赖外部服务。

import json
import logging
import time
import uuid
from contextlib import contextmanager

logging.basicConfig(level=logging.INFO, format="%(message)s")

@contextmanager
def observe(operation: str, request_id: str):
    started = time.perf_counter()
    try:
        yield
    except Exception as exc:
        elapsed_ms = round((time.perf_counter() - started) * 1000, 2)
        logging.error(json.dumps({
            "event": "operation_failed",
            "operation": operation,
            "request_id": request_id,
            "duration_ms": elapsed_ms,
            "error_type": type(exc).__name__,
        }))
        raise
    else:
        elapsed_ms = round((time.perf_counter() - started) * 1000, 2)
        logging.info(json.dumps({
            "event": "operation_succeeded",
            "operation": operation,
            "request_id": request_id,
            "duration_ms": elapsed_ms,
        }))

def load_profile(user_id: str) -> dict:
    time.sleep(0.03)
    return {"user_id": user_id, "tier": "standard"}

request_id = str(uuid.uuid4())
with observe("load_profile", request_id):
    profile = load_profile("user-42")
    print(profile)

这个例子有几个值得保留的边界:日志是结构化 JSON,耗时使用单调时钟计算,成功和失败事件使用不同的事件名,并且没有记录完整的用户资料。接入日志平台后,还可以按 operationerror_typerequest_id 查询。

在开发阶段验证“能不能观察”

可观测性不是代码部署以后才检查的属性。提交前可以问:

  • 失败路径是否产生了足够的上下文?
  • 耗时是否能定位到具体操作?
  • 日志中的请求标识是否能贯穿调用链?
  • 是否可能泄露密码、令牌、个人信息或完整支付数据?
  • 高频循环是否会产生无法承受的日志量?

可以把这些问题变成轻量的测试或代码检查。例如,针对外部 API 客户端,测试超时和异常时是否带有操作名称与请求标识:

def test_external_call_error_contains_context(caplog):
    request_id = "req-test-1"

    try:
        with observe("payment_provider", request_id):
            raise TimeoutError("provider timeout")
    except TimeoutError:
        pass

    assert "payment_provider" in caplog.text
    assert request_id in caplog.text
    assert "TimeoutError" in caplog.text

实际项目中,日志断言应配合团队现有的测试框架和日志采集方式调整。这个测试的目的不是锁定日志文本,而是保护诊断契约:关键失败发生时,必须留下能行动的信息。

从最小实践逐步接入平台

当本地信号稳定后,再决定是否接入指标、追踪和集中式日志。一个务实的推进顺序是:

  1. 为少数关键操作定义事件名和字段。
  2. 为失败、超时和慢请求增加耗时与错误信息。
  3. 在请求入口生成或接收请求标识,并向下游传递。
  4. 为高价值路径增加计数和延迟指标。
  5. 当跨服务排查成为主要成本时,再引入分布式追踪。

每一步都应有明确用途。例如,日志用于回答“这次发生了什么”,指标用于回答“问题规模多大”,追踪用于回答“时间花在了哪一段”。如果某个信号没有对应的排查问题,就应该重新评估它是否值得保留。

还需要控制三个风险:

  • 隐私风险:敏感字段采用白名单和脱敏策略。
  • 成本风险:限制高频事件的采样率、字段数量和保留时间。
  • 性能风险:避免在热路径中进行昂贵的序列化或同步网络上报。

一份适合提交前使用的清单

可观测性左移的目标,不是让每位开发者维护一套复杂平台,而是让代码在交付时具备基本的自解释能力。提交一个新功能或修复问题前,可以检查:

  • 关键成功和失败路径是否可区分;
  • 慢操作是否有耗时信息;
  • 错误是否包含操作名称和关联标识;
  • 记录内容是否经过隐私审查;
  • 信号是否足够低噪声,能够支持一个具体的排查动作。

从一条最常出问题的路径开始,通常比一次性改造整个系统更容易获得反馈。好的可观测性代码不会把实现淹没在日志里,而是在真正需要排查时,准确告诉你应该看哪里。


相关推荐