Skip to content

Day 4:可观测性——Metrics/Tracing/Logging三件套

今天做什么

网关跑起来了,但你得知道它运行得怎么样。加三件套:Prometheus 指标、请求追踪、结构化日志。

代码

go
package main

import (
    "log/slog"
    "net/http"
    "os"
    "time"

    "github.com/google/uuid"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// ===== Prometheus 指标 =====
var (
    requestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{Name: "gateway_requests_total"},
        []string{"model", "status"},
    )
    requestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "gateway_request_duration_seconds",
            Buckets: []float64{0.1, 0.5, 1, 2, 5, 10},
        },
        []string{"model"},
    )
    queueLength = prometheus.NewGauge(
        prometheus.GaugeOpts{Name: "gateway_queue_length"},
    )
)

func init() {
    prometheus.MustRegister(requestsTotal, requestDuration, queueLength)
}

// ===== 带可观测性的中间件 =====
func (g *Gateway) ObservabilityMiddleware(next http.Handler) http.Handler {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 注入 traceId
        traceID := uuid.New().String()
        ctx := context.WithValue(r.Context(), "trace_id", traceID)
        r = r.WithContext(ctx)

        start := time.Now()

        // 记录指标
        model := r.Header.Get("X-Routed-Model")
        requestsTotal.WithLabelValues(model, "started").Inc()

        // 处理请求
        next.ServeHTTP(w, r)

        // 记录耗时
        duration := time.Since(start).Seconds()
        requestDuration.WithLabelValues(model).Observe(duration)

        // 结构化日志
        logger.Info("request_completed",
            "trace_id", traceID,
            "model", model,
            "duration_ms", duration*1000,
            "queue_len", len(g.queue.queue),
        )
    })
}

// ===== 暴露指标 =====
func main() {
    gw := NewGateway()

    // Prometheus 指标端点
    http.Handle("/metrics", promhttp.Handler())

    // 网关路由 + 中间件
    handler := gw.ObservabilityMiddleware(
        http.HandlerFunc(gw.ServeHTTP))
    http.Handle("/v1/chat/completions", handler)

    log.Fatal(http.ListenAndServe(":8080", nil))
}

运行

bash
go run main.go

# 查看指标
curl http://localhost:8080/metrics
# gateway_requests_total{model="gpt-3.5-turbo",status="started"} 1234
# gateway_request_duration_seconds_bucket{model="gpt-3.5-turbo",le="0.5"} 800

Day 4 完成。网关现在可观测了。