mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-03 21:07:33 -04:00
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>
This commit is contained in:
48
core/services/routing/pii/metrics.go
Normal file
48
core/services/routing/pii/metrics.go
Normal file
@@ -0,0 +1,48 @@
|
||||
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)),
|
||||
))
|
||||
}
|
||||
@@ -58,6 +58,7 @@ type memoryEventStore struct {
|
||||
}
|
||||
|
||||
func (s *memoryEventStore) Record(_ context.Context, e PIIEvent) error {
|
||||
recordEventMetric(e)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.ring[s.cursor] = e
|
||||
|
||||
Reference in New Issue
Block a user