package observability
import (
"context"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
)
// Metrics is the steward-managed Prometheus steward. It owns a private
// Registry (no global registry leakage) and the lethe-specific collectors
// other layers increment. The HTTP middleware (Phase 5) records request
// counters/histograms here; the ingest service (Phase 7) increments the
// ingest counters.
//
// Registering everything via Registry.MustRegister in Init keeps wiring in
// one place. Cardinality control on HTTPRequests/HTTPDuration is enforced by
// the middleware: the route label must come from chi's RoutePattern (never
// the raw URL path).
type Metrics struct {
Registry *prometheus.Registry
HTTPRequests *prometheus.CounterVec
HTTPDuration *prometheus.HistogramVec
IngestLinesAccepted prometheus.Counter
IngestLinesErrored prometheus.Counter
IngestChunksCommitted prometheus.Counter
}
// Init builds a fresh registry, attaches the standard process and Go runtime
// collectors, and registers the lethe-specific HTTP and ingest series.
func (m *Metrics) Init(_ context.Context) error {
m.Registry = prometheus.NewRegistry()
m.Registry.MustRegister(
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
collectors.NewGoCollector(),
)
m.HTTPRequests = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "lethe_http_requests_total",
Help: "Total HTTP requests handled, labelled by method, chi route pattern, and status code.",
},
[]string{"method", "route", "status"},
)
m.HTTPDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "lethe_http_request_duration_seconds",
Help: "HTTP request duration in seconds, labelled by method and chi route pattern.",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "route"},
)
m.IngestLinesAccepted = prometheus.NewCounter(prometheus.CounterOpts{
Name: "lethe_ingest_lines_accepted_total",
Help: "Total ingest JSONL lines accepted (validated and queued for commit).",
})
m.IngestLinesErrored = prometheus.NewCounter(prometheus.CounterOpts{
Name: "lethe_ingest_lines_errored_total",
Help: "Total ingest JSONL lines rejected (validation, size, or schema failures).",
})
m.IngestChunksCommitted = prometheus.NewCounter(prometheus.CounterOpts{
Name: "lethe_ingest_chunks_committed_total",
Help: "Total ingest chunks successfully committed to the database.",
})
m.Registry.MustRegister(
m.HTTPRequests,
m.HTTPDuration,
m.IngestLinesAccepted,
m.IngestLinesErrored,
m.IngestChunksCommitted,
)
return nil
}