Files
LocalAI/core/services/routing/pii/metrics.go
walcz-de cc8ee62db0 feat(pii): export PII/audit events as a Prometheus counter (#10641)
The PII EventStore ring buffer is capacity-bound and meant for
recent-audit browsing via /api/pii/events; operators also want a
monotonic, scrape-friendly signal on /metrics — how many
detections/masks/blocks per hour, per origin, and whether the filter
stopped firing after a deploy (silent-failure class).

EventStore.Record is the single choke point every producer already goes
through (request middleware, response scrubbing, MITM proxy
connects/intercepts), so one lazily-initialised counter there covers all
paths without touching any producer:

  localai_pii_events_total{kind, origin, action, direction}

Same lazy otel.Meter pattern as core/services/routing/billing, so the
counter lands on the Prometheus-backed global MeterProvider installed by
the monitoring service. No behaviour change; label cardinality is
bounded (enum-like fields only, no pattern IDs or user IDs).

Assisted-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Signed-off-by: stefanwalcz <stefan.walcz@walcz.de>
2026-07-03 20:36:15 +00:00

49 lines
1.5 KiB
Go

package pii
import (
"context"
"sync"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
// Prometheus counter for PII events. The EventStore ring buffer is
// capacity-bound and meant for recent-audit browsing; operators also want
// a monotonic, scrape-friendly signal ("how many detections/blocks per
// hour, did the filter stop firing after a deploy"). Record() is the
// single choke point every producer already goes through (request
// middleware, response scrubbing, MITM proxy connects/intercepts), so one
// counter here covers all paths without touching the producers.
//
// Initialised lazily on first Record so the package works no matter when
// (or whether) the Prometheus-backed global MeterProvider is installed —
// same pattern as core/services/routing/billing.
var (
metricsOnce sync.Once
eventsCounter metric.Int64Counter
)
func recordEventMetric(e PIIEvent) {
metricsOnce.Do(func() {
meter := otel.Meter("github.com/mudler/LocalAI")
c, err := meter.Int64Counter(
"localai_pii_events_total",
metric.WithDescription("PII/audit events recorded, labeled by kind, origin, action and direction"),
)
if err == nil {
eventsCounter = c
}
})
if eventsCounter == nil {
return
}
eventsCounter.Add(context.Background(), 1, metric.WithAttributes(
attribute.String("kind", string(e.Kind)),
attribute.String("origin", string(e.Origin)),
attribute.String("action", string(e.Action)),
attribute.String("direction", string(e.Direction)),
))
}