354 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			354 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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),
 | |
| 	}
 | |
| }
 |