Skip to content

6.5 监控与可观测性 ​

Prometheus 指标 ​

go
import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// 定义指标
var (
    httpRequestsTotal = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "endpoint", "status"},
    )

    httpRequestDuration = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request duration in seconds",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "endpoint"},
    )
)

// 监控中间件
func PrometheusMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
        duration := time.Since(start).Seconds()

        httpRequestsTotal.WithLabelValues(
            c.Request.Method,
            c.FullPath(),
            fmt.Sprintf("%d", c.Writer.Status()),
        ).Inc()

        httpRequestDuration.WithLabelValues(
            c.Request.Method,
            c.FullPath(),
        ).Observe(duration)
    }
}

// 指标端点
r.GET("/metrics", gin.WrapH(promhttp.Handler()))

链路追踪 (OpenTelemetry) ​

go
import (
    "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.17.0"
)

func initTracer() (*sdktrace.TracerProvider, error) {
    exporter, _ := jaeger.New(
        jaeger.WithCollectorEndpoint(
            jaeger.WithEndpoint("http://localhost:14268/api/traces"),
        ),
    )

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

// 使用 Trace
func handleRequest(ctx context.Context) {
    tracer := otel.Tracer("myapp")
    ctx, span := tracer.Start(ctx, "handleRequest")
    defer span.End()
}

// Gin 集成
import "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
r.Use(otelgin.Middleware("myapp"))

结构化日志 ​

go
// zap
import "go.uber.org/zap"

logger, _ := zap.NewProduction()
defer logger.Sync()

logger.Info("用户登录",
    zap.String("username", "alice"),
    zap.Int("user_id", 123),
)

logger.Error("数据库错误",
    zap.String("query", sql),
    zap.Error(err),
)

// zerolog
import "github.com/rs/zerolog"
import "github.com/rs/zerolog/log"

zerolog.TimeFieldFormat = zerolog.TimeFormatISO8601
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout})

log.Info().
    Str("username", "alice").
    Int("user_id", 123).
    Msg("用户登录")

健康检查 ​

go
type HealthService struct {
    db *sql.DB
}

func (s *HealthService) Check(ctx context.Context) HealthResponse {
    response := HealthResponse{
        Status: "healthy",
        Checks: make(map[string]string),
    }

    // 数据库检查
    if err := s.db.PingContext(ctx); err != nil {
        response.Checks["database"] = "unhealthy"
        response.Status = "unhealthy"
    } else {
        response.Checks["database"] = "healthy"
    }

    return response
}

// Handler
func HealthHandler(hs *HealthService) gin.HandlerFunc {
    return func(c *gin.Context) {
        ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Second)
        defer cancel()
        health := hs.Check(ctx)

        status := http.StatusOK
        if health.Status != "healthy" {
            status = http.StatusServiceUnavailable
        }
        c.JSON(status, health)
    }
}

// 路由
r.GET("/healthz", LivenessHandler)   // liveness probe
r.GET("/readyz", ReadinessHandler)   // readiness probe
r.GET("/health", HealthHandler)      // 详细健康信息