Skip to main content

Metrics Reference

Complete catalog of all OpenTelemetry metrics exposed by Excalibur framework components.

Before You Start​

  • .NET 10.0
  • Install the required packages:
    dotnet add package Excalibur.Dispatch.Observability
    dotnet add package OpenTelemetry.Extensions.Hosting
  • Familiarity with OpenTelemetry and health checks

Quick Start​

Enable metrics collection with OpenTelemetry:

builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
// Core Dispatch metrics
metrics.AddMeter("Excalibur.Dispatch.*");

// Data layer metrics
metrics.AddMeter("Excalibur.Data.*");

// Event sourcing metrics
metrics.AddMeter("Excalibur.EventSourcing.*");
});

Naming Convention​

All meters follow the pattern: Excalibur.{Namespace}.{Component}

PrefixDescription
Excalibur.Dispatch.*Core messaging, transport, and pipeline metrics
Excalibur.Data.*Data access and persistence metrics
Excalibur.EventSourcing.*Event store and snapshot metrics

Core Metrics​

Excalibur.Dispatch.Core​

Core message dispatching metrics.

MetricTypeUnitDescription
dispatch.messages.processedCounter{messages}Total number of messages processed
dispatch.messages.publishedCounter{messages}Total number of messages published
dispatch.messages.failedCounter{messages}Total number of messages that failed
dispatch.messages.durationHistogrammsMessage processing duration
dispatch.sessions.activeGauge{sessions}Number of active sessions

Tags: message_type, handler, result


Excalibur.Dispatch.CircuitBreakerMiddleware​

State-transition and rejection counters emitted directly by CircuitBreakerMiddleware whenever the middleware is in the pipeline — no opt-in observability service required. Subscribe to this meter to alert on breaker trips out of the box.

MetricTypeUnitDescription
dispatch.circuit_breaker.transitionsCounter{transitions}Circuit breaker state transitions (emitted only on an actual state change)
dispatch.circuit_breaker.rejectionsCounter{rejections}Requests rejected because the circuit was open

Tags:

  • dispatch.circuit_breaker.transitions — circuit.key, from_state, to_state (state values: closed, open, half_open)
  • dispatch.circuit_breaker.rejections — circuit.key

Excalibur.Dispatch.PoisonMessage.Middleware​

Dead-letter routing counter emitted directly by PoisonMessageMiddleware whenever a poison message is moved to the dead-letter queue — no opt-in observability service required. Subscribe to this meter to alert on dead-letter volume out of the box.

MetricTypeUnitDescription
dispatch.poison.dead_letteredCounter{messages}Poison messages moved to the dead-letter queue

Tags: poison.detector, poison.reason


Excalibur.Dispatch.Sagas​

Saga orchestration metrics.

MetricTypeUnitDescription
dispatch.saga.started_totalCounter{sagas}Sagas initiated
dispatch.saga.completed_totalCounter{sagas}Sagas completed successfully
dispatch.saga.failed_totalCounter{sagas}Sagas that failed
dispatch.saga.compensated_totalCounter{sagas}Sagas that triggered compensation
dispatch.saga.durationHistogrammsTotal saga execution duration
dispatch.saga.handler_durationHistogrammsIndividual handler execution duration
dispatch.saga.activeObservableGauge{sagas}Currently active sagas

Tags: saga_type, state, handler


Excalibur.Dispatch.BackgroundServices​

Background processor metrics (outbox, inbox, CDC).

MetricTypeUnitDescription
excalibur.background_service.processing_cyclesCounter{cycles}Processing cycles executed
excalibur.background_service.messages_processedCounter{messages}Messages processed
excalibur.background_service.messages_failedCounter{messages}Messages that failed processing
excalibur.background_service.processing_durationHistogrammsProcessing cycle duration
excalibur.background_service.processing_errorsCounter{errors}Processing cycle errors

Tags: service_type (outbox, inbox, cdc), operation


Transport Metrics​

Excalibur.Dispatch.Transport​

Common transport layer metrics (all transports).

MetricTypeUnitDescription
dispatch.transport.messages.sentCounter{messages}Messages sent
dispatch.transport.messages.send_failedCounter{messages}Message send failures
dispatch.transport.messages.receivedCounter{messages}Messages received
dispatch.transport.messages.acknowledgedCounter{messages}Messages acknowledged
dispatch.transport.messages.rejectedCounter{messages}Messages rejected
dispatch.transport.messages.dead_letteredCounter{messages}Messages routed to dead letter queue
dispatch.transport.messages.requeuedCounter{messages}Messages requeued for redelivery
dispatch.transport.send.durationHistogrammsSend operation duration
dispatch.transport.receive.durationHistogrammsReceive operation duration
dispatch.transport.batch.sizeHistogram{messages}Batch sizes
dispatch.transport.handler.errorsCounter{errors}Handler errors during subscriber processing
dispatch.transport.handler.durationHistogrammsSubscriber handler invocation duration

Tags: dispatch.transport.name, dispatch.transport.destination, and error.type on the failure counters. (Prometheus renders these as dispatch_transport_name, dispatch_transport_destination, and error_type.)


Excalibur.Dispatch.Transport.GooglePubSub​

Google Cloud Pub/Sub metrics. All instruments below share the meter Excalibur.Dispatch.Transport.GooglePubSub.

Streaming pull — emitted by the streaming-pull subscriber:

MetricTypeUnitDescription
dispatch.streaming_pull.messages.receivedCounter{messages}Messages received via streaming pull
dispatch.streaming_pull.acks.sentCounter{acks}Acknowledgments sent
dispatch.streaming_pull.nacks.sentCounter{nacks}Negative acknowledgments sent
dispatch.streaming_pull.streams.openedCounter{streams}Streams opened
dispatch.streaming_pull.streams.closedCounter{streams}Streams closed
dispatch.streaming_pull.streams.reconnectionsCounter{reconnections}Stream reconnection attempts
dispatch.streaming_pull.message.processing_durationHistogrammsMessage processing duration
dispatch.streaming_pull.ack.latencyHistogrammsAcknowledgment latency

Batch receiving — emitted when batch receiving is enabled:

MetricTypeUnitDescription
pubsub.batch.messages.receivedCounter{messages}Messages received in batches
pubsub.batch.messages.acknowledgedCounter{messages}Messages acknowledged
pubsub.batch.bytes.receivedCounterByBytes received in batches
pubsub.batch.countCounter{batches}Batches received
pubsub.batch.sizeHistogram{messages}Size of received batches
pubsub.batch.receive.durationHistogrammsBatch receive duration
pubsub.batch.ack.durationHistogrammsBatch acknowledgment duration
pubsub.batch.processors.activeUpDownCounter{processors}Active batch processors
Connection, flow-control, and queue-time instruments are not emitted

A broader Pub/Sub meter covering enqueue/dequeue counts, connection open/close, flow-control permits, and queue time is defined in the package, but no subscriber path records to it. Only the instruments listed above produce data points.


Azure Storage Queues​

Azure Storage Queue support ships inside the Excalibur.Dispatch.Transport.AzureServiceBus package.

No queue-specific metrics are emitted

The Storage Queue transport publishes no meter of its own, and there is no Storage Queue meter to subscribe to.

Its registration also does not apply the common transport telemetry decorators, so the Excalibur.Dispatch.Transport instruments are not emitted for this transport either. For Storage Queue throughput, instrument your queue handlers directly, or use the queue metrics Azure Monitor publishes from the Storage account itself.


Data Layer Metrics​

Excalibur.Data.Persistence​

No provider-agnostic persistence metrics are emitted

There is no provider-agnostic persistence meter. Nothing publishes an Excalibur.Data.Persistence meter or activity source, so there is nothing to subscribe to at that level.

Use the provider-specific meters below instead. They are registered by the provider's own Add… extension and are emitted in normal operation:

For connection-pool utilization, scrape the counters published by the underlying ADO.NET provider (Microsoft.Data.SqlClient or Npgsql) rather than this framework.


Excalibur.Data.SqlServer.Persistence​

SQL Server specific persistence metrics.

MetricTypeUnitDescription
sqlserver.connections.createdCounter{connections}Connections created
sqlserver.queries.executedCounter{queries}Queries executed
sqlserver.commands.executedCounter{commands}Commands executed
sqlserver.transactions.startedCounter{transactions}Transactions started
sqlserver.transactions.committedCounter{transactions}Transactions committed
sqlserver.transactions.rolledbackCounter{transactions}Transactions rolled back
sqlserver.retriesCounter{retries}Retry operations
sqlserver.errorsCounter{errors}Error count
sqlserver.deadlocksCounter{deadlocks}Deadlock count
sqlserver.query.durationHistogrammsQuery duration
sqlserver.command.durationHistogrammsCommand duration
sqlserver.transaction.durationHistogrammsTransaction duration
sqlserver.connection.wait_timeHistogrammsConnection wait time
sqlserver.batch.sizeHistogram{commands}Batch sizes
sqlserver.connections.activeObservableGauge{connections}Active connections
sqlserver.transactions.activeObservableGauge{transactions}Active transactions
sqlserver.cdc.events.processedCounter{events}CDC events processed
sqlserver.cdc.processing.durationHistogrammsCDC processing duration
sqlserver.cdc.lagObservableGaugesCDC lag
sqlserver.cache.hitsCounter{hits}Cache hits
sqlserver.cache.missesCounter{misses}Cache misses
sqlserver.cache.hit_ratioObservableGauge1Cache hit ratio

Tags: database, operation, result


Excalibur.Data.Postgres.Persistence​

PostgreSQL specific persistence metrics.

MetricTypeUnitDescription
postgres.queries.totalCounter{queries}Total queries
postgres.commands.totalCounter{commands}Total commands
postgres.transactions.totalCounter{transactions}Total transactions
postgres.queries.failedCounter{queries}Failed queries
postgres.commands.failedCounter{commands}Failed commands
postgres.transactions.failedCounter{transactions}Failed transactions
postgres.connections.errorsCounter{errors}Connection errors
postgres.timeouts.totalCounter{timeouts}Timeout count
postgres.deadlocks.totalCounter{deadlocks}Deadlock count
postgres.cache.hitsCounter{hits}Cache hits
postgres.cache.missesCounter{misses}Cache misses
postgres.query.durationHistogrammsQuery duration
postgres.command.durationHistogrammsCommand duration
postgres.transaction.durationHistogrammsTransaction duration
postgres.connection.acquisition.timeHistogrammsConnection acquisition time
postgres.prepared.statementsObservableGauge{statements}Prepared statement count

Tags: database, operation, result


Excalibur.Outbox.Postgres​

Meter name: Excalibur.Outbox.Postgres. Add it explicitly, or with the Excalibur.Outbox.* wildcard — the Excalibur.Data.* wildcard in the Quick Start does not match this meter.

PostgreSQL outbox store metrics.

MetricTypeUnitDescription
excalibur.outbox.save_messages_durationHistogrammsTime taken to save outbox messages
excalibur.outbox.reserve_messages_durationHistogrammsTime taken to reserve outbox messages
excalibur.outbox.delete_record_durationHistogrammsTime taken to delete an outbox record
excalibur.outbox.increase_attempts_durationHistogrammsTime taken to increase message attempts
excalibur.outbox.move_to_dead_letter_durationHistogrammsTime taken to move a message to dead letter
excalibur.outbox.batch_delete_durationHistogrammsTime taken to delete multiple outbox records
excalibur.outbox.batch_increase_attempts_durationHistogrammsTime taken to increase attempts for multiple messages
excalibur.outbox.batch_move_to_dead_letter_durationHistogrammsTime taken to move multiple messages to dead letter
excalibur.outbox.messages_processed_totalCounter{messages}Total number of outbox messages processed
excalibur.outbox.operations_completed_totalCounter{operations}Total number of outbox operations completed

Tags: operation, result


Compliance Metrics​

Excalibur.Compliance​

Security and compliance metrics.

MetricTypeUnitDescription
dispatch.compliance.key_rotationsCounter{rotations}Key rotations performed
dispatch.compliance.key_rotation_failuresCounter{failures}Key rotation failures
dispatch.compliance.encryption_latencyHistogrammsEncryption operation latency
dispatch.compliance.encryption_operationsCounter{operations}Encryption operations
dispatch.compliance.encryption_bytes_processedCounterByBytes encrypted/decrypted
dispatch.compliance.audit_events_loggedCounter{events}Audit events logged
dispatch.compliance.audit_integrity_checksCounter{checks}Integrity checks performed
dispatch.compliance.audit_integrity_violationsCounter{violations}Integrity violations detected
dispatch.compliance.audit_integrity_check_durationHistogrammsIntegrity check duration
dispatch.compliance.key_usage_operationsCounter{operations}Key usage operations

Tags: key_id, algorithm, result


Caching Metrics​

Excalibur.Dispatch.Caching​

Caching middleware metrics.

MetricTypeUnitDescription
dispatch.cache.hitsCounter-Cache hits
dispatch.cache.missesCounter-Cache misses
dispatch.cache.timeoutsCounter-Cache operation timeouts
dispatch.cache.durationHistogrammsCache operation latency

Tags: cache_name, operation, result


Context Flow Metrics​

Excalibur.Dispatch.Observability.Context​

Message context flow and preservation metrics.

MetricTypeUnitDescription
dispatch.context.flow.snapshotsCounter{snapshots}Context snapshots taken
dispatch.context.flow.mutationsCounter{mutations}Context mutations
dispatch.context.flow.errorsCounter{errors}Context errors
dispatch.context.flow.validation_failuresCounter{failures}Validation failures
dispatch.context.flow.cross_boundary_transitionsCounter{transitions}Cross-boundary transitions
dispatch.context.flow.preservation_successCounter{successes}Successful context preservation
dispatch.context.flow.field_lossCounter{losses}Context field loss events
dispatch.context.flow.size_threshold_exceededCounter{events}Size threshold exceeded
dispatch.context.flow.size_bytesHistogramByContext size distribution
dispatch.context.flow.field_countHistogram{fields}Field count distribution
dispatch.context.flow.stage_latencyHistogrammsPipeline stage latency
dispatch.context.flow.serialization_latencyHistogrammsSerialization latency
dispatch.context.flow.deserialization_latencyHistogrammsDeserialization latency
dispatch.context.flow.active_contextsObservableGauge{contexts}Active contexts
dispatch.context.flow.preservation_rateObservableGauge1Context preservation rate
dispatch.context.flow.lineage_depthObservableGauge{depth}Lineage depth

Tags: stage, boundary_type, result


Prometheus Query Examples​

Deriving the Prometheus series name​

The tables above list OpenTelemetry instrument names. The Prometheus exporter renames every instrument on the way out, so the series you query is never the instrument name verbatim. With OpenTelemetry.Exporter.Prometheus.AspNetCore, the rules are applied in this order:

  1. Sanitize — every character outside [A-Za-z0-9:] becomes _, and runs of _ collapse to one. So dispatch.circuit_breaker.rejections becomes dispatch_circuit_breaker_rejections.
  2. Append the unit — the instrument's unit is appended as a suffix unless the name already ends with it. UCUM abbreviations are expanded (ms becomes milliseconds, By becomes bytes, s becomes seconds); the dimensionless unit 1 is dropped; and a UCUM annotation — a unit written in braces, such as {messages} — is stripped entirely, so a count of things keeps its meaning in the Unit column without adding anything to the series name.
  3. Append _total to counters whose name does not already end in _total.
  4. Histograms additionally expose _bucket, _sum, and _count series.

Label keys are sanitized the same way, so the circuit.key tag is queried as circuit_key.

Every instrument in the tables above declares a UCUM unit, so step 2 never appends a stray word: dispatch.messages.processed carries the annotation {messages} and exports as dispatch_messages_processed_total, and a duration carrying ms exports with a single _milliseconds suffix. Histograms whose unit is a duration are therefore the only series that gain a unit word.

Message Throughput​

# Messages processed per second
rate(dispatch_messages_processed_total[5m])

# Error rate
rate(dispatch_messages_failed_total[5m]) / rate(dispatch_messages_processed_total[5m])

Latency Percentiles​

# P99 message processing latency
histogram_quantile(0.99, sum(rate(dispatch_messages_duration_milliseconds_bucket[5m])) by (le))

# P50 (median) processing latency
histogram_quantile(0.50, sum(rate(dispatch_messages_duration_milliseconds_bucket[5m])) by (le))

Circuit Breaker Health​

There is no circuit-breaker state gauge — the middleware emits state transitions, so track trips and rejections rather than reading a current state:

# Trips into the open state, per second, by circuit
sum by (circuit_key) (rate(dispatch_circuit_breaker_transitions_total{to_state="open"}[5m]))

# Requests rejected because the circuit was open
sum by (circuit_key) (rate(dispatch_circuit_breaker_rejections_total[5m]))

Database Performance​

# Average query duration (milliseconds)
rate(sqlserver_query_duration_milliseconds_sum[5m])
/ rate(sqlserver_query_duration_milliseconds_count[5m])

# Deadlocks per second
rate(postgres_deadlocks_total[5m])
Connection pools

There is no connection-pool utilization instrument in this framework. Npgsql and Microsoft.Data.SqlClient publish their own pool counters — scrape those.


Grafana Dashboard Templates​

Pre-built Grafana dashboards are available in the grafana-dashboards documentation.

Key Dashboards​

DashboardDescription
Dispatch OverviewMessage throughput, latency, error rates
Transport HealthPer-transport metrics and connection status
Circuit BreakersCircuit states across all breakers
Database PerformanceQuery latency, connection pools, deadlocks
Compliance MonitoringEncryption operations, key rotation, audit trail

See Also​

  • Production Observability — Operational best practices for monitoring Dispatch in production environments
  • Health Checks — Application health monitoring with built-in and custom health check endpoints
  • Grafana Dashboards — Pre-built Grafana dashboard templates for visualizing Dispatch metrics