Go 可观测性

Go 服务可观测性体系,涵盖日志、指标、链路追踪三大支柱与 OpenTelemetry 集成实践

#type / concept #status / growing #tech / dev #resource / go

[!info] related notes

Go 可观测性

一句话定义

Go 可观测性通过日志(logs)、指标(metrics)、链路追踪(traces)三大支柱,配合 OpenTelemetry SDK 实现对分布式服务的全面监控与故障排查。

核心机制 / 工作原理

三大支柱:

支柱回答的问题Go 标准方案存储后端
日志发生了什么?log/slog(Go 1.21+)Loki, ELK
指标系统状态如何?Prometheus client_golangPrometheus, M3
链路追踪请求经过了哪里?OpenTelemetry Go SDKJaeger, Tempo

OpenTelemetry Go SDK 初始化:

package main

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)

func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) {
    exporter, err := otlptracegrpc.New(ctx,
        otlptracegrpc.WithEndpoint("otel-collector:4317"),
        otlptracegrpc.WithInsecure(),
    )
    if err != nil {
        return nil, err
    }

    tp := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceName("myapp"),
            semconv.ServiceVersion("v1.0.0"),
        )),
    )
    otel.SetTracerProvider(tp)
    return tp, nil
}

Prometheus 指标注册:

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
    "net/http"
)

var (
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total HTTP requests",
        },
        []string{"method", "path", "status"},
    )
    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "path"},
    )
)

func init() {
    prometheus.MustRegister(httpRequestsTotal, httpRequestDuration)
}

func MetricsHandler() http.Handler {
    return promhttp.Handler()
}

结构化日志(slog):

import "log/slog"

logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
}))

logger.Info("request processed",
    "method", "GET",
    "path", "/api/users",
    "status", 200,
    "duration_ms", 42,
)

最小例子 / 最小场景

在 HTTP handler 中集成三大支柱:

func handleRequest(w http.ResponseWriter, r *http.Request) {
    start := time.Now()

    // 链路追踪
    tracer := otel.Tracer("myapp")
    ctx, span := tracer.Start(r.Context(), "handleRequest")
    defer span.End()

    // 业务逻辑
    result, err := doWork(ctx)

    // 指标
    status := "200"
    if err != nil {
        status = "500"
        span.RecordError(err)
    }
    httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, status).Inc()
    httpRequestDuration.WithLabelValues(r.Method, r.URL.Path).
        Observe(time.Since(start).Seconds())

    // 日志
    slog.InfoContext(ctx, "request handled",
        "status", status,
        "duration", time.Since(start),
    )
}

为什么重要

  1. 故障排查效率:没有可观测性的服务是黑盒。日志告诉你发生了什么,指标告诉你影响范围,追踪告诉你问题在哪一步——三者缺一不可。
  2. SLI/SLO 驱动:指标是定义和监控服务水平目标(如 p99 延迟 < 200ms)的基础。
  3. 分布式系统必需:微服务架构下,一个请求可能跨越 10+ 个服务,链路追踪是唯一能还原完整调用链的手段。
  4. OpenTelemetry 统一:OTel 成为行业标准,一套 SDK 同时覆盖 traces、metrics、logs,避免厂商锁定。

边界与易混淆点

  • context 传播是前提:OpenTelemetry 依赖 context.Context 传递 trace ID 和 span ID。如果 goroutine 没有正确传递 context,链路就会断裂。
  • Exemplars 的作用:Exemplars 将指标数据点关联到具体的 trace ID,在 Grafana 中可以从异常指标直接跳转到对应的 trace,是连接指标和追踪的桥梁。
  • 日志 vs 追踪事件:日志是独立的、时间有序的记录;追踪事件是某个 span 上下文中的结构化数据。不要把所有日志都塞进 span,也不要让日志缺少 trace ID。
  • 采样策略:生产环境不可能保留 100% 的 traces。Head-based 采样(入口处决定)简单但可能丢失有价值的异常 trace。Tail-based 采样(收集后决定)更智能但需要 Collector 层支持。
  • log/slog vs zap/zerologslog 是 Go 1.21+ 标准库,API 稳定但性能略低于 zap。新项目推荐 slog,已有 zap 的项目无需迁移。
  • 指标命名规范:遵循 Prometheus 命名约定({namespace}_{subsystem}_{name}_{unit}),如 http_request_duration_seconds。不规范的命名会导致 PromQL 查询困难。
创建于 2026/6/25 更新于 2026/6/25