Add EventBroadcaster metrics for observability and debugging
All checks were successful
CI / build (pull_request) Successful in 38s

- Add BroadcasterMetrics interface for reading metrics per namespace
- Add MetricsCollector interface and DefaultMetricsCollector implementation
- Track events_published and events_received counters per namespace
- Track active_subscriptions gauge per namespace
- Track publish_errors, subscribe_errors, and dropped_events counters
- Add MetricsProvider interface for EventBroadcaster implementations
- Integrate metrics tracking into EventBus and NATSEventBus
- Add optional Prometheus integration via PrometheusMetricsAdapter
- Add comprehensive unit tests for metrics functionality

Closes #22

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-10 19:51:23 +01:00
parent 9e238c5e70
commit dae751a6ef
7 changed files with 782 additions and 11 deletions

View File

@@ -24,6 +24,13 @@ type EventBroadcaster interface {
SubscriberCount(namespaceID string) int
}
// MetricsProvider is an optional interface that EventBroadcaster implementations
// can implement to expose metrics.
type MetricsProvider interface {
// Metrics returns the metrics collector for this broadcaster.
Metrics() BroadcasterMetrics
}
// subscription represents a single subscriber channel with its pattern
type subscription struct {
pattern string
@@ -45,6 +52,7 @@ type EventBus struct {
mutex sync.RWMutex
ctx context.Context
cancel context.CancelFunc
metrics *DefaultMetricsCollector
}
// NewEventBus creates a new event bus
@@ -55,9 +63,15 @@ func NewEventBus() *EventBus {
wildcardSubscribers: make([]subscription, 0),
ctx: ctx,
cancel: cancel,
metrics: NewMetricsCollector(),
}
}
// Metrics returns the metrics collector for this event bus.
func (eb *EventBus) Metrics() BroadcasterMetrics {
return eb.metrics
}
// Subscribe creates a new subscription channel for a namespace pattern.
// Patterns follow NATS subject matching conventions:
// - "*" matches a single token (any sequence without ".")
@@ -84,6 +98,9 @@ func (eb *EventBus) Subscribe(namespacePattern string) <-chan *Event {
eb.exactSubscribers[namespacePattern] = append(eb.exactSubscribers[namespacePattern], ch)
}
// Record subscription metric
eb.metrics.RecordSubscribe(namespacePattern)
return ch
}
@@ -98,6 +115,8 @@ func (eb *EventBus) Unsubscribe(namespacePattern string, ch <-chan *Event) {
if sub.ch == ch {
eb.wildcardSubscribers = append(eb.wildcardSubscribers[:i], eb.wildcardSubscribers[i+1:]...)
close(sub.ch)
// Record unsubscription metric
eb.metrics.RecordUnsubscribe(namespacePattern)
break
}
}
@@ -109,6 +128,8 @@ func (eb *EventBus) Unsubscribe(namespacePattern string, ch <-chan *Event) {
// Remove channel from slice
eb.exactSubscribers[namespacePattern] = append(subs[:i], subs[i+1:]...)
close(subscriber)
// Record unsubscription metric
eb.metrics.RecordUnsubscribe(namespacePattern)
break
}
}
@@ -128,14 +149,19 @@ func (eb *EventBus) Publish(namespaceID string, event *Event) {
eb.mutex.RLock()
defer eb.mutex.RUnlock()
// Record publish metric
eb.metrics.RecordPublish(namespaceID)
// Deliver to exact subscribers
subscribers := eb.exactSubscribers[namespaceID]
for _, ch := range subscribers {
select {
case ch <- event:
// Event delivered
eb.metrics.RecordReceive(namespaceID)
default:
// Channel full, skip this subscriber (non-blocking)
eb.metrics.RecordDroppedEvent(namespaceID)
}
}
@@ -145,8 +171,10 @@ func (eb *EventBus) Publish(namespaceID string, event *Event) {
select {
case sub.ch <- event:
// Event delivered
eb.metrics.RecordReceive(namespaceID)
default:
// Channel full, skip this subscriber (non-blocking)
eb.metrics.RecordDroppedEvent(namespaceID)
}
}
}
@@ -159,16 +187,18 @@ func (eb *EventBus) Stop() {
eb.cancel()
// Close all exact subscriber channels
for _, subs := range eb.exactSubscribers {
// Close all exact subscriber channels and update metrics
for namespaceID, subs := range eb.exactSubscribers {
for _, ch := range subs {
close(ch)
eb.metrics.RecordUnsubscribe(namespaceID)
}
}
// Close all wildcard subscriber channels
// Close all wildcard subscriber channels and update metrics
for _, sub := range eb.wildcardSubscribers {
close(sub.ch)
eb.metrics.RecordUnsubscribe(sub.pattern)
}
eb.exactSubscribers = make(map[string][]chan *Event)