package middleware import ( "net/http" "strconv" "time" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" ) var ( // HTTP метрики httpRequestsTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total number of HTTP requests", }, []string{"method", "path", "status", "handler"}, ) httpRequestDuration = promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: "http_request_duration_seconds", Help: "Duration of HTTP requests", Buckets: []float64{0.01, 0.05, 0.1, 0.5, 1, 2.5, 5, 10}, }, []string{"method", "path", "handler"}, ) // GraphQL специфичные метрики gqlOperationsTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "graphql_operations_total", Help: "Total number of GraphQL operations", }, []string{"operation", "type", "name", "success"}, ) gqlOperationDuration = promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: "graphql_operation_duration_seconds", Help: "Duration of GraphQL operations", Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2, 5}, }, []string{"operation", "name"}, ) // Бизнес метрики usersTotal = promauto.NewGauge( prometheus.GaugeOpts{ Name: "users_total", Help: "Total number of registered users", }, ) postsTotal = promauto.NewGauge( prometheus.GaugeOpts{ Name: "posts_total", Help: "Total number of posts", }, ) commentsTotal = promauto.NewGauge( prometheus.GaugeOpts{ Name: "comments_total", Help: "Total number of comments", }, ) messagesTotal = promauto.NewGauge( prometheus.GaugeOpts{ Name: "messages_total", Help: "Total number of messages", }, ) activeWebsockets = promauto.NewGauge( prometheus.GaugeOpts{ Name: "websocket_connections_active", Help: "Number of active WebSocket connections", }, ) websocketMessagesTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "websocket_messages_total", Help: "Total number of WebSocket messages", }, []string{"direction", "type"}, ) // Метрики ошибок errorsTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "errors_total", Help: "Total number of errors by type", }, []string{"type", "source", "severity"}, ) // Метрики базы данных dbQueriesTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "db_queries_total", Help: "Total number of database queries", }, []string{"operation", "table", "success"}, ) dbQueryDuration = promauto.NewHistogramVec( prometheus.HistogramOpts{ Name: "db_query_duration_seconds", Help: "Duration of database queries", Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 2}, }, []string{"operation", "table"}, ) // Метрики кэша cacheHitsTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "cache_hits_total", Help: "Total number of cache hits", }, []string{"type", "name"}, ) cacheMissesTotal = promauto.NewCounterVec( prometheus.CounterOpts{ Name: "cache_misses_total", Help: "Total number of cache misses", }, []string{"type", "name"}, ) ) // MetricsMiddleware собирает метрики для Prometheus func MetricsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() rw := &responseWriter{w, http.StatusOK, 0} next.ServeHTTP(rw, r) duration := time.Since(start).Seconds() status := strconv.Itoa(rw.status) // Определяем handler type handlerType := "http" if r.URL.Path == "/query" { handlerType = "graphql" } else if r.URL.Path == "/ws" { handlerType = "websocket" } httpRequestsTotal.WithLabelValues( r.Method, r.URL.Path, status, handlerType, ).Inc() httpRequestDuration.WithLabelValues( r.Method, r.URL.Path, handlerType, ).Observe(duration) }) } // GraphQLMetricsMiddleware для отслеживания GraphQL операций func GraphQLMetricsMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/query" { next.ServeHTTP(w, r) return } // Здесь можно парсить GraphQL запрос и извлекать информацию об операции // Для простоты пока пропускаем next.ServeHTTP(w, r) }) } } // Вспомогательные функции для обновления метрик func IncGQLOperation(operationType, operationName string, success bool, duration time.Duration) { status := "false" if success { status = "true" } gqlOperationsTotal.WithLabelValues( operationType, // operation operationName, // type (возможно нужно переименовать) operationName, // name (дублирование) status, // success ).Inc() gqlOperationDuration.WithLabelValues( operationType, operationName, ).Observe(duration.Seconds()) } func IncWebSocketMessage(direction, messageType string) { websocketMessagesTotal.WithLabelValues(direction, messageType).Inc() } func SetActiveWebsockets(count int) { activeWebsockets.Set(float64(count)) } func IncError(errorType, source, severity string) { errorsTotal.WithLabelValues(errorType, source, severity).Inc() } func IncDBQuery(operation, table string, success bool, duration time.Duration) { status := "false" if success { status = "true" } dbQueriesTotal.WithLabelValues(operation, table, status).Inc() dbQueryDuration.WithLabelValues(operation, table).Observe(duration.Seconds()) } func IncCacheHit(cacheType, cacheName string) { cacheHitsTotal.WithLabelValues(cacheType, cacheName).Inc() } func IncCacheMiss(cacheType, cacheName string) { cacheMissesTotal.WithLabelValues(cacheType, cacheName).Inc() } func SetUsersCount(count int) { usersTotal.Set(float64(count)) } func SetPostsCount(count int) { postsTotal.Set(float64(count)) } func SetCommentsCount(count int) { commentsTotal.Set(float64(count)) } func SetMessagesCount(count int) { messagesTotal.Set(float64(count)) }