MetricsMiddleware — Metrics Reporting Middleware¶
Overview¶
MetricsMiddleware is a zero-dependency, lightweight metrics collection middleware
providing Prometheus / OpenTelemetry-style instrumentation. Pure in-memory implementation
— no need to install prometheus_client or opentelemetry-api.
Get a read-only snapshot via snapshot(), or bridge to the official SDKs with a single
call to integrate_prometheus() / integrate_opentelemetry() to expose metrics to
Prometheus scrape endpoints or OTLP Collectors.
Collected Metrics¶
| Metric Name | Type | Description |
|---|---|---|
infinity_bus_publish_total |
Counter | Publish count by event name (including failed attempts) |
infinity_bus_publish_duration |
Histogram | Publish latency (before_publish → end of on_publish) |
infinity_bus_publish_errors_total |
Counter | Publish failure count |
infinity_bus_queue_size |
Gauge | Current event queue depth |
infinity_bus_active_tasks |
Gauge | Current active handler task count |
infinity_bus_task_total |
Counter | Task completion count by handler + outcome (success / error / cancelled / skipped) |
infinity_bus_task_duration |
Histogram | Task execution latency (after semaphore acquisition → end) |
infinity_bus_task_wait |
Histogram | Task queue latency (dispatch → semaphore acquisition) |
Use Cases¶
- Observability: Monitor event bus throughput, latency, and error rates.
- Performance analysis: Identify slow events or handlers via latency histograms.
- Capacity planning: Observe queue depth and active tasks to assess scaling needs.
- Alerting: Integrate with Prometheus + Grafana or OpenTelemetry + Jaeger for alerts.
- Debugging: Quickly inspect bus runtime state via
snapshot()during development.
Function Signature¶
MetricsMiddleware¶
class MetricsMiddleware(Middleware):
def __init__(self, histogram_bounds: tuple[float, ...] = _DEFAULT_BUCKETS) -> None
| Parameter | Type | Description |
|---|---|---|
histogram_bounds |
tuple[float, ...] |
Histogram bucket upper bounds in seconds. Default covers 1ms–30s: (0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0). |
Methods¶
| Method | Description |
|---|---|
snapshot() -> MetricsSnapshot |
Returns a read-only snapshot of all current metrics. |
integrate_prometheus(registry=None) |
Bridges to the prometheus_client SDK. After calling, metrics are synced to official Counter / Histogram / Gauge. |
integrate_opentelemetry(meter=None) |
Bridges to the opentelemetry-api SDK. After calling, metrics are synced to OTel Counter / Histogram / Gauge. |
Properties¶
| Property | Type | Description |
|---|---|---|
prometheus_integrated |
bool |
Whether bridged to prometheus_client. |
opentelemetry_integrated |
bool |
Whether bridged to opentelemetry-api. |
Data Models¶
MetricsSnapshot¶
class MetricsSnapshot(BaseModel):
publish_total: Dict[str, int] # {event_name: publish count}
publish_duration_sec: Dict[str, Buckets] # {event_name: latency histogram}
publish_errors_total: int # Total publish failures
queue_size: int # Current queue depth
active_tasks: int # Current active handler tasks
task_total: Dict[str, Dict[str, int]] # {handler_name: {outcome: count}}
task_duration_sec: Dict[str, Buckets] # {handler_name: execution latency histogram}
task_wait_sec: Dict[str, Buckets] # {handler_name: queue latency histogram}
Buckets¶
class Buckets(BaseModel):
count: int # Total observations
sum: float # Sum of observed values (seconds)
buckets: Dict[str, int] # {upper_bound_string: count}
inf: int # Samples exceeding the largest bucket bound
Workflow¶
sequenceDiagram
participant P as Publisher
participant M as MetricsMiddleware
participant N as Downstream/Core
P->>M: before_publish(name, data)
M->>M: t0 = time.perf_counter()
M->>N: next(...)
alt Success
N-->>M: Return
else Exception
N-->>M: Raise
M->>M: errors_total += 1
M-->>P: Re-raise
end
M->>M: elapsed = perf_counter() - t0
M->>M: publish_total[name] += 1
M->>M: histogram.observe(elapsed)
Key points:
- Timing starts on
before_publishentry and ends after the core publish flow (includingon_publishchain), covering the complete publish latency. - Failed publishes count toward both
publish_totalandpublish_errors_total(following the Prometheus_totalconvention). queue_sizeandactive_tasksare instantaneous values, retrieved viasnapshot()or Gauge callbacks.- Task metrics are updated in
on_task_done(notification-style, never affecting task execution):task_totalcounts by outcome;task_durationrecords execution latency (after semaphore acquisition → end);task_waitrecords queue latency (dispatch → semaphore acquisition).
Usage Examples¶
Pure In-Memory Mode¶
from infinity_bus.templates.middlewares import MetricsMiddleware
from infinity_bus import MiddlewareChain
metrics = MetricsMiddleware()
chain = MiddlewareChain()
chain.add(metrics)
bus = EventBus(events, handlers, middleware_chain=chain)
async with bus:
await bus.proxy("svc").publish("order.created", {...})
await bus.proxy("svc").publish("order.paid", {...})
# Get snapshot
snap = metrics.snapshot()
print(snap.publish_total)
# {'order.created': 1, 'order.paid': 1}
print(snap.publish_duration_sec["order.created"])
# Buckets(count=1, sum=0.0032, buckets={'0.001': 0, '0.005': 1, ...}, inf=0)
Integrating with Prometheus SDK¶
import prometheus_client
metrics = MetricsMiddleware()
# Bridge to a custom Registry (omit to use the global REGISTRY)
registry = prometheus_client.CollectorRegistry()
metrics.integrate_prometheus(registry)
# Start HTTP exposition endpoint
prometheus_client.start_http_server(8000, registry=registry)
# Metrics are now synced to official Counter / Histogram / Gauge
# Visit http://localhost:8000/metrics to scrape
Integrating with OpenTelemetry SDK¶
from opentelemetry import metrics as otel_metrics
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import (
ConsoleMetricExporter,
PeriodicExportingMetricReader,
)
# Configure OTel SDK
reader = PeriodicExportingMetricReader(ConsoleMetricExporter())
provider = MeterProvider(metric_readers=[reader])
otel_metrics.set_meter_provider(provider)
meter = otel_metrics.get_meter("infinity_bus")
metrics = MetricsMiddleware()
metrics.integrate_opentelemetry(meter)
# Metrics are exported via OTLP exporter to Collector / Jaeger / Prometheus
Custom Bucket Boundaries¶
# Custom latency buckets: 10ms, 50ms, 100ms, 500ms, 1s, 5s
metrics = MetricsMiddleware(histogram_bounds=(0.01, 0.05, 0.1, 0.5, 1.0, 5.0))
Composing with Other Middlewares¶
from infinity_bus.templates.middlewares import (
MetricsMiddleware,
RateLimitMiddleware,
JSONLLoggingMiddleware,
)
chain = MiddlewareChain()
chain.add(RateLimitMiddleware(max_requests=100, window_seconds=1.0))
chain.add(metrics := MetricsMiddleware())
chain.add(JSONLLoggingMiddleware("events.jsonl"))
# Execution order: rate limit → metrics collection → logging
# Metrics will record rate-limited rejected events (counted in publish_total + errors_total)
Notes¶
- Zero-dependency by default: Without
prometheus_client/opentelemetry-api, pure in-memory mode works fully;snapshot()functions normally. - Lazy bridging: The corresponding SDK is only imported when
integrate_prometheus()/integrate_opentelemetry()is called. - publish_total includes failures: Following the Prometheus
_totalconvention, all publish attempts (including failures) are counted inpublish_total; failures are additionally counted inpublish_errors_total. - Gauges use callbacks:
queue_sizeandactive_tasksare collected via the SDK's callback /set_functionmechanism, reading the latest value on each scrape. - Idempotent bridging: Repeated calls to
integrate_*methods do not create duplicate metrics. - Snapshot is read-only:
snapshot()returns a deep copy ofMetricsSnapshot; modifications do not affect internal state.
Internals¶
- Pure in-memory histogram
_Histogram: cumulative bucketing, no external dependencies. - Timing uses
time.perf_counter(), a high-precision monotonic clock. try...finallyinbefore_publishensures latency and error counts are recorded even in exception scenarios.- Prometheus Gauges use
set_functionfor lazy evaluation. - OpenTelemetry Gauges use
create_observable_gauge+ callback for lazy evaluation.
Full Example¶
See tests/templates/middlewares/metrics_test.py
Contains test cases for basic counting, latency histograms, per-event independent counters, error counting, snapshot immutability, custom bucket boundaries, and Prometheus SDK bridging.