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) // 详细健康信息