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