Go service如何搭建自己的可观测性?

举报
golang学习记 发表于 2026/08/24 17:34:46 2026/08/24
【摘要】 那天凌晨两点,支付服务又开始报超时了。我打开 Kibana,熟练地输入 "error",回车。三千条结果。再输入 "timeout",一千二。然后我开始了那场熟悉的“猜谜游戏”:翻日志、对时间戳、开另一个窗口看监控、切回来继续翻、怀疑是数据库、怀疑是网络、怀疑是缓存、怀疑人生。三个小时后,我终于找到了根因——一个上游依赖的慢查询。但其实前半个小时,所有的信息都已经躺在日志里了。我只是没法把碎...

那天凌晨两点,支付服务又开始报超时了。我打开 Kibana,熟练地输入 "error",回车。三千条结果。再输入 "timeout",一千二。然后我开始了那场熟悉的“猜谜游戏”:翻日志、对时间戳、开另一个窗口看监控、切回来继续翻、怀疑是数据库、怀疑是网络、怀疑是缓存、怀疑人生。

三个小时后,我终于找到了根因——一个上游依赖的慢查询。但其实前半个小时,所有的信息都已经躺在日志里了。我只是没法把碎片拼起来。

那一刻我不得不面对一个尴尬的事实:我们的服务有日志、有监控、甚至还有追踪,但遇到问题时,这些东西并没有让我更快地找到答案。

我们把“可观测性”当成了一堆工具的集合,而不是一种回答问题的能力


第一步:别打“叙事日志”了,打“结构化日志”

把 Go 的日志从自由文本换成结构化日志,是性价比最高的升级之一。

像这样的日志:

log.Printf("failed to fetch order: %v", err)

人看着还行,但排查问题的时候基本没用。换成这样就好多了:

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("request completed",
    "service", "order-api",
    "route", "/v1/orders/{id}",
    "status_code", 200,
    "duration_ms", 34,
    "request_id", "req_123",
)

这一个改动会改变后续所有事情:你现在可以按 request_id 过滤、按 route 分组、按 status_code 搜索,跨服务用统一的属性把日志串起来。

我的原则很简单:每行生产日志,首先得能让机器查,其次才是让人读。


第二步:请求关联——日志孤岛就是事故现场的噩梦

日志在事故期间感觉没用,最大的原因是它们彼此孤立。你有了完美的结构化日志,但如果不能把它们和请求、追踪、上游调用关联起来,依然是在浪费时间。

所以我现在会标准化一组关联字段:

  • trace_id
  • span_id
  • request_id
  • service
  • route
  • 需要的话再加 user_id

在 Go 里,就是在请求边界把 trace context 拽进日志:

func LoggerWithTrace(ctx context.Context, logger *slog.Logger) *slog.Logger {
    span := trace.SpanFromContext(ctx)
    sc := span.SpanContext()
    if !sc.IsValid() {
        return logger
    }
    return logger.With(
        "trace_id", sc.TraceID().String(),
        "span_id", sc.SpanID().String(),
    )
}

一旦你稳定地这么做,调试就从“搜报错信息”变成了“跟着请求走”。后者要舒服太多了。


第三步:追踪要能解释“时间去哪了”,不只是“有没有”

很多团队说自己“有追踪”,其实就是一个请求一个根 span,下面啥也没有。这不够。

对于后端系统,最有价值的 span 通常是:

  • 入站 HTTP 请求
  • 数据库查询
  • 出站 HTTP/RPC 调用
  • 消息队列发布/消费
  • 耗时的内部计算

比如追踪一个支付调用:

func callPaymentService(ctx context.Context, client *http.Client, url string) (*http.Response, error) {
    ctx, span := tracer.Start(ctx, "payment_client.charge")
    defer span.End()

    req, _ := http.NewRequestWithContext(ctx, http.MethodPost, url, nil)
    span.SetAttributes(attribute.String("http.url", url))

    resp, err := client.Do(req)
    if err != nil {
        span.RecordError(err)
        return nil, err
    }
    span.SetAttributes(attribute.Int("http.status_code", resp.StatusCode))
    return resp, nil
}

关键不是“追踪一切”,而是“追踪能解释延迟和失败的那部分”。

如果事故期间我打开一个 trace,我希望很快看到一个明确的答案:延迟是在我们的 handler、数据库,还是支付服务? 如果 trace 回答不了这个问题,它就是装饰品。


第四步:指标要反映“服务行为”,而不是“实现细节”

这地方最容易搞臃肿。团队加了几十个 counter 和 gauge,因为“加上又不费事”,然后发现没有一个能回答有意义的运维问题。

我一般从一小套稳定的指标开始:

  • 请求总数
  • 请求耗时分布(histogram)
  • 正在处理的请求数(in-flight)
  • 错误数/错误率
  • 依赖延迟和依赖错误
  • 相关的时候再加队列深度或 worker 饱和度

用 Prometheus 的话大概是这样:

var (
    httpRequests = prometheus.NewCounterVec(
        prometheus.CounterOpts{Name: "http_requests_total"},
        []string{"route", "method", "status_code"},
    )
    httpDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Buckets: prometheus.DefBuckets,
        },
        []string{"route", "method"},
    )
    inFlight = prometheus.NewGauge(
        prometheus.GaugeOpts{Name: "http_requests_in_flight"},
    )
)

一个实用建议:保持 labels 低基数。用 /orders/{id} 这样的路由模板,别用原始用户 ID。这个习惯能让你的 Prometheus 指标保持有用,而不是“贵得离谱”。


第五步:仪表盘应该在一屏之内回答事故问题

我现在想要的 Go 服务仪表盘,其实挺小的:

  • 请求速率
  • 错误率
  • p50 / p95 / p99 延迟
  • 正在处理的请求数
  • 报错最多的路由
  • 报错最多的依赖
  • 最近部署标记
  • 从 trace 跳转到日志、从日志跳转到 trace 的链接

就这些。

如果我在事故头五分钟还需要另外十个图表才能看懂,那这个仪表盘就没帮上什么忙。


重新设计之后发生了什么变化?

最先改善的不是监控覆盖率,是调试速度

事故变短了,因为遥测数据开始指向原因,而不是制造噪音。结构化日志让搜索变得可行,追踪让依赖延迟变得可见,指标说明了问题是本地的、上游的还是系统性的。关联字段把孤立事件串成了一个完整的请求故事。

第二个改善是文化上的

开发者不再为了“安心”而乱加日志,开始问更好的问题:如果这个接口在 p95 时挂了,我需要知道什么?哪个 span 能解释那个延迟?哪个指标能在用户抱怨之前告警?哪些日志属性能让我们在几秒内隔离出一条坏请求?

当这些问题开始出现,可观测性就不再是一个“平台打勾项”了——它成了软件设计的一部分。


最后说几句

在 Go 里做有效的可观测性,不是收集更多信号。而是确保每个信号都有自己的职责:

  • 结构化日志提供详细上下文
  • 追踪解释请求流向和依赖耗时
  • 指标反映服务健康状况和告警依据

Go 已经给了你很好的基础组件:slog 做结构化日志,OpenTelemetry 做追踪,Prometheus client 做指标。真正的提升来自于有目的地组合它们

一旦日志带上关联 ID、追踪能展示依赖成本、指标能反映真实服务行为——可观测性就不再是摆设了。它开始真正帮上忙。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。