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), | ||
|  | 	} | ||
|  | } |