@@ -21,51 +21,69 @@ import (
2121// Compile-time interface check.
2222var _ core.AuditLogger = (* AuditService )(nil )
2323
24- // auditEventsDropped is a singleton counter registered once via sync.Once
25- // to avoid duplicate-registration panics when multiple AuditService
26- // instances are created (e.g. in tests) .
24+ // auditEventsDropped is a singleton counter protected by a mutex so it can
25+ // be created before metrics are configured and registered later once a
26+ // registerer becomes available .
2727//
2828// The counter is only registered with Prometheus when a registerer is
2929// explicitly provided via SetAuditMetricsRegisterer, so deployments with
3030// metrics disabled do not leak collectors from the services layer.
3131var (
3232 auditEventsDropped prometheus.Counter
33- auditEventsDroppedOnce sync.Once
33+ auditEventsDroppedMu sync.Mutex
34+ auditEventsDroppedRegistered bool
3435 auditEventsDroppedRegisterer prometheus.Registerer
3536)
3637
38+ // registerAuditDroppedCounterLocked attempts to register the existing counter
39+ // with the configured registerer. The caller must hold auditEventsDroppedMu.
40+ func registerAuditDroppedCounterLocked () {
41+ if auditEventsDropped == nil ||
42+ auditEventsDroppedRegisterer == nil ||
43+ auditEventsDroppedRegistered {
44+ return
45+ }
46+ if err := auditEventsDroppedRegisterer .Register (auditEventsDropped ); err != nil {
47+ if existing , ok := err .(prometheus.AlreadyRegisteredError ); ok {
48+ if c , ok := existing .ExistingCollector .(prometheus.Counter ); ok {
49+ auditEventsDropped = c
50+ auditEventsDroppedRegistered = true
51+ return
52+ }
53+ }
54+ log .Printf ("failed to register audit dropped-events counter: %v" , err )
55+ return
56+ }
57+ auditEventsDroppedRegistered = true
58+ }
59+
3760// SetAuditMetricsRegisterer configures the Prometheus registerer used by the
38- // audit service. It must be called before any AuditService is created in order
39- // for the dropped-events counter to be registered with Prometheus.
61+ // audit service. If the dropped-events counter was created before metrics
62+ // were configured, setting a non-nil registerer will register the existing
63+ // counter, ensuring late initialization (e.g. in tests) is not silently lost.
4064func SetAuditMetricsRegisterer (registerer prometheus.Registerer ) {
65+ auditEventsDroppedMu .Lock ()
66+ defer auditEventsDroppedMu .Unlock ()
67+
4168 auditEventsDroppedRegisterer = registerer
69+ registerAuditDroppedCounterLocked ()
4270}
4371
4472func getAuditEventsDroppedCounter () prometheus.Counter {
45- auditEventsDroppedOnce .Do (func () {
73+ auditEventsDroppedMu .Lock ()
74+ defer auditEventsDroppedMu .Unlock ()
75+
76+ if auditEventsDropped == nil {
4677 // Use a single fully-prefixed Name (no Namespace/Subsystem) to
4778 // match the convention used by metrics in internal/metrics.
48- opts := prometheus.CounterOpts {
79+ auditEventsDropped = prometheus . NewCounter ( prometheus.CounterOpts {
4980 Name : "audit_events_dropped_total" ,
5081 Help : "Total number of audit log events dropped due to a full buffer." ,
51- }
52- counter := prometheus .NewCounter (opts )
53-
54- if auditEventsDroppedRegisterer != nil {
55- if err := auditEventsDroppedRegisterer .Register (counter ); err != nil {
56- if existing , ok := err .(prometheus.AlreadyRegisteredError ); ok {
57- if c , ok := existing .ExistingCollector .(prometheus.Counter ); ok {
58- auditEventsDropped = c
59- return
60- }
61- }
62- log .Printf ("failed to register audit dropped-events counter: %v" , err )
63- }
64- }
65- // When no registerer is set, the counter still works in-memory but
66- // is not exposed via the Prometheus /metrics endpoint.
67- auditEventsDropped = counter
68- })
82+ })
83+ }
84+ registerAuditDroppedCounterLocked ()
85+ // When no registerer is set, the counter still works in-memory but
86+ // is not exposed via the Prometheus /metrics endpoint.
6987 return auditEventsDropped
7088}
7189
0 commit comments