otpm/metrics/metrics.go
“xHuPo” bcd986e3f7 beta
2025-05-23 18:57:11 +08:00

193 lines
4.5 KiB
Go

package metrics
import (
"fmt"
"net/http"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
// Default metrics
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "path", "status"},
)
requestTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
otpGenerationTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "otp_generation_total",
Help: "Total number of OTP generations",
},
[]string{"user_id", "otp_id"},
)
otpVerificationTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "otp_verification_total",
Help: "Total number of OTP verifications",
},
[]string{"user_id", "otp_id", "success"},
)
activeUsers = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "active_users",
Help: "Number of active users",
},
)
cacheHits = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "cache_hits_total",
Help: "Total number of cache hits",
},
[]string{"cache"},
)
cacheMisses = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "cache_misses_total",
Help: "Total number of cache misses",
},
[]string{"cache"},
)
)
func init() {
// Register metrics with prometheus
prometheus.MustRegister(
requestDuration,
requestTotal,
otpGenerationTotal,
otpVerificationTotal,
activeUsers,
cacheHits,
cacheMisses,
)
}
// MetricsService provides metrics functionality
type MetricsService struct {
activeUsersMutex sync.RWMutex
activeUserIDs map[string]bool
}
// NewMetricsService creates a new MetricsService
func NewMetricsService() *MetricsService {
return &MetricsService{
activeUserIDs: make(map[string]bool),
}
}
// Handler returns an HTTP handler for metrics
func (s *MetricsService) Handler() http.Handler {
return promhttp.Handler()
}
// RecordRequest records metrics for an HTTP request
func (s *MetricsService) RecordRequest(method, path string, status int, duration time.Duration) {
labels := prometheus.Labels{
"method": method,
"path": path,
"status": fmt.Sprintf("%d", status),
}
requestDuration.With(labels).Observe(duration.Seconds())
requestTotal.With(labels).Inc()
}
// RecordOTPGeneration records metrics for OTP generation
func (s *MetricsService) RecordOTPGeneration(userID, otpID string) {
otpGenerationTotal.With(prometheus.Labels{
"user_id": userID,
"otp_id": otpID,
}).Inc()
}
// RecordOTPVerification records metrics for OTP verification
func (s *MetricsService) RecordOTPVerification(userID, otpID string, success bool) {
otpVerificationTotal.With(prometheus.Labels{
"user_id": userID,
"otp_id": otpID,
"success": fmt.Sprintf("%t", success),
}).Inc()
}
// RecordUserActivity records user activity
func (s *MetricsService) RecordUserActivity(userID string) {
s.activeUsersMutex.Lock()
defer s.activeUsersMutex.Unlock()
if !s.activeUserIDs[userID] {
s.activeUserIDs[userID] = true
activeUsers.Inc()
}
}
// RecordUserInactivity records user inactivity
func (s *MetricsService) RecordUserInactivity(userID string) {
s.activeUsersMutex.Lock()
defer s.activeUsersMutex.Unlock()
if s.activeUserIDs[userID] {
delete(s.activeUserIDs, userID)
activeUsers.Dec()
}
}
// RecordCacheHit records a cache hit
func (s *MetricsService) RecordCacheHit(cache string) {
cacheHits.With(prometheus.Labels{
"cache": cache,
}).Inc()
}
// RecordCacheMiss records a cache miss
func (s *MetricsService) RecordCacheMiss(cache string) {
cacheMisses.With(prometheus.Labels{
"cache": cache,
}).Inc()
}
// Middleware creates a middleware that records request metrics
func (s *MetricsService) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Create response writer that captures status code
rw := &responseWriter{ResponseWriter: w, status: http.StatusOK}
// Call next handler
next.ServeHTTP(rw, r)
// Record metrics
s.RecordRequest(r.Method, r.URL.Path, rw.status, time.Since(start))
})
}
// responseWriter wraps http.ResponseWriter to capture status code
type responseWriter struct {
http.ResponseWriter
status int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.status = code
rw.ResponseWriter.WriteHeader(code)
}