原文链接:Go项目可观测性:Metrics、Logging、Tracing全面指南
在现代Go应用中,可观测性(Observability)是确保系统可靠性和可维护性的关键。它主要包含三个支柱:Metrics(指标)、Logging(日志)和Tracing(追踪)。下面我将详细介绍如何在Go项目中实现完整的可观测性方案。
1. Metrics(指标)
指标是系统运行状态的量化数据,通常用于监控和告警。
常用库
- Prometheus: 最流行的监控系统
- expvar: Go标准库内置
- OpenTelemetry Metrics: 跨平台指标收集
实现示例
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
requestsTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "path"},
)
)
func init() {
prometheus.MustRegister(requestsTotal)
prometheus.MustRegister(requestDuration)
}
func main() {
http.Handle("/metrics", promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
timer := prometheus.NewTimer(requestDuration.WithLabelValues(r.Method, r.URL.Path))
defer timer.ObserveDuration()
// 业务逻辑
w.Write([]byte("Hello World"))
requestsTotal.WithLabelValues(r.Method, r.URL.Path, "200").Inc()
})
http.ListenAndServe(":8080", nil)
}
2. Logging(日志)
日志记录系统运行时的详细信息,用于调试和审计。
常用库
- Zap (uber-go/zap): 高性能
- Logrus (sirupsen/logrus): 功能丰富
- zerolog (rs/zerolog): 零分配JSON日志
结构化日志示例
import (
"os"
"go.uber.org/zap"
)
func main() {
// 生产环境配置
logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
// 开发环境更易读的日志
// logger, _ := zap.NewDevelopment()
logger.Info("Starting application",
zap.String("version", "1.0.0"),
zap.Int("port", 8080),
)
logger.Error("Failed to connect to DB",
zap.String("url", "postgres://user:pass@localhost/db"),
zap.Error(err),
)
// 使用Sugar()简化日志记录,性能稍低但更易用
sugar := logger.Sugar()
sugar.Infow("User logged in",
"userID", 123,
"ip", "192.168.1.1",
)
}
3. Tracing(追踪)
追踪用于分析请求在分布式系统中的流转和性能。
常用方案
- OpenTelemetry: 行业标准
- Jaeger: 流行的分布式追踪系统
- Zipkin: 另一种追踪系统
OpenTelemetry实现示例
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
)
func initTracer(url string) (*sdktrace.TracerProvider, error) {
// 创建Jaeger exporter
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("my-service"),
semconv.ServiceVersionKey.String("1.0.0"),
)),
)
otel.SetTracerProvider(tp)
return tp, nil
}
func main() {
tp, err := initTracer("http://localhost:14268/api/traces")
if err != nil {
panic(err)
}
defer tp.Shutdown(context.Background())
tracer := otel.Tracer("main")
ctx, span := tracer.Start(context.Background(), "main-function")
defer span.End()
// 业务逻辑
doWork(ctx)
}
func doWork(ctx context.Context) {
tracer := otel.Tracer("work")
_, span := tracer.Start(ctx, "doWork")
defer span.End()
// 模拟工作
span.AddEvent("starting work")
// ...
span.AddEvent("work completed")
}
三者的协同使用
最佳实践是将Metrics、Logging和Tracing关联起来:
func handleRequest(w http.ResponseWriter, r *http.Request) {
// 从请求中提取或生成TraceID
ctx := r.Context()
traceID := trace.SpanFromContext(ctx).SpanContext().TraceID().String()
// 记录日志时包含TraceID
logger := zap.L().With(zap.String("trace_id", traceID))
// 记录指标时包含TraceID
requestsTotal.WithLabelValues(r.Method, r.URL.Path, "200").Inc()
// 业务逻辑...
logger.Info("Request processed")
}
进阶建议
- 采样策略:对追踪数据实施采样,避免数据量过大
- 日志级别:合理使用DEBUG/INFO/WARN/ERROR等级别
- 上下文传播:确保跨服务调用时传递TraceID
- Grafana集成:可视化指标和日志
- 告警规则:基于指标设置合理的告警阈值
部署架构示例
[Go应用] -> [OpenTelemetry Collector] -> [Prometheus(指标)]
\-----> [Loki(日志)]
\-----> [Jaeger(追踪)]
通过以上方案,你可以构建一个完整的可观测性体系,帮助你在生产环境中快速定位和解决问题,同时了解系统的运行状态。