Go 可观测性
Go 服务可观测性体系,涵盖日志、指标、链路追踪三大支柱与 OpenTelemetry 集成实践
#type / concept
#status / growing
#tech / dev
#resource / go
[!info] related notes
- 所属 MOC: Go 服务工程
- 前置概念: Go Context
- 并列概念: Go Docker 部署
Go 可观测性
一句话定义
Go 可观测性通过日志(logs)、指标(metrics)、链路追踪(traces)三大支柱,配合 OpenTelemetry SDK 实现对分布式服务的全面监控与故障排查。
核心机制 / 工作原理
三大支柱:
| 支柱 | 回答的问题 | Go 标准方案 | 存储后端 |
|---|---|---|---|
| 日志 | 发生了什么? | log/slog(Go 1.21+) | Loki, ELK |
| 指标 | 系统状态如何? | Prometheus client_golang | Prometheus, M3 |
| 链路追踪 | 请求经过了哪里? | OpenTelemetry Go SDK | Jaeger, 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),
)
}
为什么重要
- 故障排查效率:没有可观测性的服务是黑盒。日志告诉你发生了什么,指标告诉你影响范围,追踪告诉你问题在哪一步——三者缺一不可。
- SLI/SLO 驱动:指标是定义和监控服务水平目标(如 p99 延迟 < 200ms)的基础。
- 分布式系统必需:微服务架构下,一个请求可能跨越 10+ 个服务,链路追踪是唯一能还原完整调用链的手段。
- 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/slogvszap/zerolog:slog是 Go 1.21+ 标准库,API 稳定但性能略低于zap。新项目推荐slog,已有zap的项目无需迁移。- 指标命名规范:遵循 Prometheus 命名约定(
{namespace}_{subsystem}_{name}_{unit}),如http_request_duration_seconds。不规范的命名会导致 PromQL 查询困难。