From a4c910a099c11ebe4e6112eb75226617bdbfd746 Mon Sep 17 00:00:00 2001 From: Naeel Date: Sun, 12 Apr 2026 12:04:54 +0300 Subject: [PATCH] feat(metrics): integrate Prometheus metrics into router and main - /metrics endpoint via promhttp.Handler - actionHandler: request counter, bytes counter, latency histogram, errors - Gauge updater: queues/messages count per tenant every 15s - All metrics labeled by tenant and/or operation --- app/cmd/goaws.go | 4 ++++ app/router/router.go | 21 ++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/cmd/goaws.go b/app/cmd/goaws.go index ee64df1..a0f4546 100644 --- a/app/cmd/goaws.go +++ b/app/cmd/goaws.go @@ -16,6 +16,7 @@ import ( "shared-sqs/app/billing" "shared-sqs/app/conf" "shared-sqs/app/gosqs" + "shared-sqs/app/metrics" "shared-sqs/app/models" "shared-sqs/app/persistence" "shared-sqs/app/router" @@ -136,6 +137,9 @@ func main() { quit := make(chan bool) go gosqs.PeriodicTasks(1*time.Second, quit) + // Metrics: gauge updater — пересчёт очередей/сообщений per tenant каждые 15 секунд + metrics.StartGaugeUpdater(15*time.Second, quit) + // HTTP сервер с таймаутами srv := &http.Server{ Addr: "0.0.0.0:" + port, diff --git a/app/router/router.go b/app/router/router.go index d8ef3a8..5e4092f 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -11,15 +11,19 @@ import ( "net/http" "strings" + "time" + "shared-sqs/app/admin" "shared-sqs/app/auth" "shared-sqs/app/billing" sqs "shared-sqs/app/gosqs" "shared-sqs/app/interfaces" + "shared-sqs/app/metrics" "shared-sqs/app/tenant" "shared-sqs/app/ui" "github.com/gorilla/mux" + "github.com/prometheus/client_golang/prometheus/promhttp" log "github.com/sirupsen/logrus" ) @@ -34,6 +38,9 @@ func New(tenantStore *tenant.TenantStore, adminToken string) http.Handler { // /health — публичный, без auth r.HandleFunc("/health", health).Methods("GET") + // /metrics — Prometheus endpoint для Victoria Metrics scrape + r.Handle("/metrics", promhttp.Handler()).Methods("GET") + // Admin API — Bearer token auth, регистрируется через AdminHandler adminHandler := admin.NewHandler(tenantStore, adminToken) adminHandler.RegisterRoutes(r) @@ -124,6 +131,7 @@ func health(w http.ResponseWriter, req *http.Request) { func actionHandler(w http.ResponseWriter, req *http.Request) { action := extractAction(req) + start := time.Now() log.WithFields(log.Fields{ "action": action, "url": req.URL, @@ -132,7 +140,11 @@ func actionHandler(w http.ResponseWriter, req *http.Request) { if ok { statusCode, responseBody := jsonFn(req) encodeResponse(w, req, statusCode, responseBody) - // Billing: записываем каждую успешную SQS-операцию (async, no-op если billing выключен) + + // Metrics: latency гистограмма для каждой операции + duration := time.Since(start).Seconds() + metrics.RequestDuration.WithLabelValues(action).Observe(duration) + if statusCode < 400 { if t, _ := req.Context().Value(auth.TenantContextKey).(*tenant.Tenant); t != nil { msgBytes := req.ContentLength @@ -144,8 +156,15 @@ func actionHandler(w http.ResponseWriter, req *http.Request) { if queueName == "" { queueName = req.FormValue("QueueName") } + // Billing: запись в PostgreSQL (async, no-op если billing выключен) billing.RecordUsage(t.ID, action, queueName, 1, msgBytes) + // Prometheus counters: requests + bytes + metrics.RequestsTotal.WithLabelValues(t.ID, action).Inc() + metrics.RequestBytesTotal.WithLabelValues(t.ID, action).Add(float64(msgBytes)) } + } else { + // Prometheus: счётчик ошибок + metrics.ErrorsTotal.WithLabelValues(action).Inc() } return }