Skip to content

OpenTelemetry integration

Modulus emits standard System.Diagnostics telemetry — no vendor SDK, no required dependency. Wire it to OpenTelemetry in the host.

What Modulus emits

SignalSource / MeterWhat you get
TracesModulus.Mediator (ActivitySource)One span per mediator request via TracingBehavior: request type, outcome (success / failure with error count and first error code / exception)
TracesModulus.Messaging.Outbox (ActivitySource)One producer span per outbox dispatch: message id, event type, outcome (published / skipped_unknown_type / deserialize_failed / retry_pending / dead_lettered), attempt number
MetricsModulus.Mediator (Meter)modulus.mediator.handler.duration histogram (ms) tagged with handler and outcome via MetricsBehavior
MetricsModulus.Messaging (Meter)Outbox and consumer pipeline instruments — see the table below

The Modulus.Messaging meter

Registered automatically by AddModulusMessaging(...); works with or without metrics DI (IMeterFactory is optional).

InstrumentTypeTagsMeaning
modulus.messaging.outbox.messagesCounteroutcome (published / skipped_unknown_type / deserialize_failed / retry_pending / dead_lettered)Outbox dispatch attempts by outcome
modulus.messaging.outbox.wakeupsCounterreason (signal / poll / backlog)Outbox processor wake-ups. A deployment showing only poll is not receiving change notifications (e.g. dedicated-worker topology) and runs at poll-interval latency
modulus.messaging.consumer.handler.durationHistogram (ms)handler, outcome (success / failure)Integration event handler execution time
modulus.messaging.inbox.deduplicatedCounterhandlerHandler executions skipped by inbox idempotency
modulus.messaging.consumer.retriesCountermessage_typeIn-process consumer retry attempts
modulus.messaging.consumer.dead_letteredCountermessage_typeMessages handed to the transport for dead-lettering

There is deliberately no pending-outbox gauge: an observable gauge would run a database query on the metrics collection thread every scrape. Backlog depth is a readiness concern — use the messaging health checks (modulus_messaging_outbox) instead, or chart outbox.messages outcome rates.

Registration

TracingBehavior is opt-in, like every pipeline behavior. Register it early so the span wraps the rest of the pipeline:

csharp
builder.Services.AddModulusMediator();
builder.Services.AddPipelineBehavior(typeof(TracingBehavior<,>));
builder.Services.AddPipelineBehavior(typeof(MetricsBehavior<,>));

Then subscribe the sources in your OpenTelemetry setup:

csharp
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource("Modulus.Mediator")
        .AddSource("Modulus.Messaging.Outbox")
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter())
    .WithMetrics(metrics => metrics
        .AddMeter("Modulus.Mediator")
        .AddMeter("Modulus.Messaging")
        .AddOtlpExporter());

With Aspire, the scaffolded ServiceDefaults project already configures the OTLP exporter — adding the two sources and the two meters is all that's needed.

Released under the MIT License.