package metrics import ( "net/http" "strconv" "sync" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/zap" ) // PrometheusMetrics Prometheus指标收集器 type PrometheusMetrics struct { logger *zap.Logger registry *prometheus.Registry mutex sync.RWMutex // 预定义指标 httpRequests *prometheus.CounterVec httpDuration *prometheus.HistogramVec activeUsers prometheus.Gauge dbConnections prometheus.Gauge cacheHits *prometheus.CounterVec businessMetrics map[string]prometheus.Collector } // NewPrometheusMetrics 创建Prometheus指标收集器 func NewPrometheusMetrics(logger *zap.Logger) *PrometheusMetrics { registry := prometheus.NewRegistry() // HTTP请求计数器 httpRequests := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total number of HTTP requests", }, []string{"method", "path", "status"}, ) // HTTP请求耗时直方图 httpDuration := prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "HTTP request duration in seconds", Buckets: prometheus.DefBuckets, }, []string{"method", "path"}, ) // 活跃用户数 activeUsers := prometheus.NewGauge( prometheus.GaugeOpts{ Name: "active_users_total", Help: "Current number of active users", }, ) // 数据库连接数 dbConnections := prometheus.NewGauge( prometheus.GaugeOpts{ Name: "database_connections_active", Help: "Current number of active database connections", }, ) // 缓存命中率 cacheHits := prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "cache_operations_total", Help: "Total number of cache operations", }, []string{"operation", "result"}, ) // 注册指标 registry.MustRegister(httpRequests) registry.MustRegister(httpDuration) registry.MustRegister(activeUsers) registry.MustRegister(dbConnections) registry.MustRegister(cacheHits) return &PrometheusMetrics{ logger: logger, registry: registry, httpRequests: httpRequests, httpDuration: httpDuration, activeUsers: activeUsers, dbConnections: dbConnections, cacheHits: cacheHits, businessMetrics: make(map[string]prometheus.Collector), } } // RecordHTTPRequest 记录HTTP请求指标 func (m *PrometheusMetrics) RecordHTTPRequest(method, path string, statusCode int, duration float64) { status := strconv.Itoa(statusCode) m.httpRequests.WithLabelValues(method, path, status).Inc() m.httpDuration.WithLabelValues(method, path).Observe(duration) m.logger.Debug("Recorded HTTP request metric", zap.String("method", method), zap.String("path", path), zap.String("status", status), zap.Float64("duration", duration)) } // RecordHTTPDuration 记录HTTP请求耗时 func (m *PrometheusMetrics) RecordHTTPDuration(method, path string, duration float64) { m.httpDuration.WithLabelValues(method, path).Observe(duration) m.logger.Debug("Recorded HTTP duration metric", zap.String("method", method), zap.String("path", path), zap.Float64("duration", duration)) } // IncrementCounter 增加计数器 func (m *PrometheusMetrics) IncrementCounter(name string, labels map[string]string) { if counter, exists := m.getOrCreateCounter(name, labels); exists { if vec, ok := counter.(*prometheus.CounterVec); ok { vec.With(labels).Inc() } } } // RecordGauge 记录仪表盘值 func (m *PrometheusMetrics) RecordGauge(name string, value float64, labels map[string]string) { if gauge, exists := m.getOrCreateGauge(name, labels); exists { if vec, ok := gauge.(*prometheus.GaugeVec); ok { vec.With(labels).Set(value) } else if g, ok := gauge.(prometheus.Gauge); ok { g.Set(value) } } } // RecordHistogram 记录直方图值 func (m *PrometheusMetrics) RecordHistogram(name string, value float64, labels map[string]string) { if histogram, exists := m.getOrCreateHistogram(name, labels); exists { if vec, ok := histogram.(*prometheus.HistogramVec); ok { vec.With(labels).Observe(value) } } } // RegisterCounter 注册计数器 func (m *PrometheusMetrics) RegisterCounter(name, help string, labels []string) error { m.mutex.Lock() defer m.mutex.Unlock() if _, exists := m.businessMetrics[name]; exists { return nil // 已存在 } var counter prometheus.Collector if len(labels) > 0 { counter = prometheus.NewCounterVec( prometheus.CounterOpts{Name: name, Help: help}, labels, ) } else { counter = prometheus.NewCounter( prometheus.CounterOpts{Name: name, Help: help}, ) } if err := m.registry.Register(counter); err != nil { return err } m.businessMetrics[name] = counter m.logger.Info("Registered counter metric", zap.String("name", name)) return nil } // RegisterGauge 注册仪表盘 func (m *PrometheusMetrics) RegisterGauge(name, help string, labels []string) error { m.mutex.Lock() defer m.mutex.Unlock() if _, exists := m.businessMetrics[name]; exists { return nil } var gauge prometheus.Collector if len(labels) > 0 { gauge = prometheus.NewGaugeVec( prometheus.GaugeOpts{Name: name, Help: help}, labels, ) } else { gauge = prometheus.NewGauge( prometheus.GaugeOpts{Name: name, Help: help}, ) } if err := m.registry.Register(gauge); err != nil { return err } m.businessMetrics[name] = gauge m.logger.Info("Registered gauge metric", zap.String("name", name)) return nil } // RegisterHistogram 注册直方图 func (m *PrometheusMetrics) RegisterHistogram(name, help string, labels []string, buckets []float64) error { m.mutex.Lock() defer m.mutex.Unlock() if _, exists := m.businessMetrics[name]; exists { return nil } if buckets == nil { buckets = prometheus.DefBuckets } var histogram prometheus.Collector if len(labels) > 0 { histogram = prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: name, Help: help, Buckets: buckets, }, labels, ) } else { histogram = prometheus.NewHistogram( prometheus.HistogramOpts{ Name: name, Help: help, Buckets: buckets, }, ) } if err := m.registry.Register(histogram); err != nil { return err } m.businessMetrics[name] = histogram m.logger.Info("Registered histogram metric", zap.String("name", name)) return nil } // GetHandler 获取HTTP处理器 func (m *PrometheusMetrics) GetHandler() http.Handler { return promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}) } // 内部辅助方法 func (m *PrometheusMetrics) getOrCreateCounter(name string, labels map[string]string) (prometheus.Collector, bool) { m.mutex.RLock() counter, exists := m.businessMetrics[name] m.mutex.RUnlock() if !exists { // 自动创建计数器 labelNames := make([]string, 0, len(labels)) for k := range labels { labelNames = append(labelNames, k) } if err := m.RegisterCounter(name, "Auto-created counter", labelNames); err != nil { m.logger.Error("Failed to auto-create counter", zap.String("name", name), zap.Error(err)) return nil, false } m.mutex.RLock() counter, exists = m.businessMetrics[name] m.mutex.RUnlock() } return counter, exists } func (m *PrometheusMetrics) getOrCreateGauge(name string, labels map[string]string) (prometheus.Collector, bool) { m.mutex.RLock() gauge, exists := m.businessMetrics[name] m.mutex.RUnlock() if !exists { labelNames := make([]string, 0, len(labels)) for k := range labels { labelNames = append(labelNames, k) } if err := m.RegisterGauge(name, "Auto-created gauge", labelNames); err != nil { m.logger.Error("Failed to auto-create gauge", zap.String("name", name), zap.Error(err)) return nil, false } m.mutex.RLock() gauge, exists = m.businessMetrics[name] m.mutex.RUnlock() } return gauge, exists } func (m *PrometheusMetrics) getOrCreateHistogram(name string, labels map[string]string) (prometheus.Collector, bool) { m.mutex.RLock() histogram, exists := m.businessMetrics[name] m.mutex.RUnlock() if !exists { labelNames := make([]string, 0, len(labels)) for k := range labels { labelNames = append(labelNames, k) } if err := m.RegisterHistogram(name, "Auto-created histogram", labelNames, nil); err != nil { m.logger.Error("Failed to auto-create histogram", zap.String("name", name), zap.Error(err)) return nil, false } m.mutex.RLock() histogram, exists = m.businessMetrics[name] m.mutex.RUnlock() } return histogram, exists } // UpdateActiveUsers 更新活跃用户数 func (m *PrometheusMetrics) UpdateActiveUsers(count float64) { m.activeUsers.Set(count) } // UpdateDBConnections 更新数据库连接数 func (m *PrometheusMetrics) UpdateDBConnections(count float64) { m.dbConnections.Set(count) } // RecordCacheOperation 记录缓存操作 func (m *PrometheusMetrics) RecordCacheOperation(operation, result string) { m.cacheHits.WithLabelValues(operation, result).Inc() } // GetStats 获取指标统计 func (m *PrometheusMetrics) GetStats() map[string]interface{} { m.mutex.RLock() defer m.mutex.RUnlock() return map[string]interface{}{ "registered_metrics": len(m.businessMetrics), } }