Adds OpenTelemetry-compatible metrics and tracing to Shuttle.Mediator by observing the Sending/Sent events already exposed on MediatorOptions — no changes are required to your participants.
dotnet add package Shuttle.Mediator.OpenTelemetryservices.AddMediator()
.AddOpenTelemetry();This subscribes to Sending/Sent on the shared MediatorOptions:
- Metrics – every send is recorded against a
MeternamedShuttle.Mediator. - Tracing – every send is wrapped in an
Activityfrom anActivitySourcenamedShuttle.Mediator, named after the message type and parented to whateverActivity.Currentis ambient at the time.
The instrumentation is built on System.Diagnostics.ActivitySource / System.Diagnostics.Metrics.Meter directly, so this package has no dependency on the OpenTelemetry SDK — it only becomes "live" once something subscribes to those names, for example:
services.AddOpenTelemetry()
.WithTracing(builder => builder.AddSource("Shuttle.Mediator"))
.WithMetrics(builder => builder.AddMeter("Shuttle.Mediator"));If you only want one of the two, call MediatorOptions.AddOpenTelemetryMetrics() / AddOpenTelemetryTracing() directly instead of AddOpenTelemetry():
services.AddOptions<MediatorOptions>().Configure(options => options.AddOpenTelemetryMetrics());| Name | Instrument | Unit | Description |
|---|---|---|---|
mediator.sends.started |
Counter | {send} |
Number of message sends started. |
mediator.sends.completed |
Counter | {send} |
Number of message sends that completed successfully. |
mediator.send.duration |
Histogram | ms |
Duration of a message send, from Sending to Sent. |
All three are tagged with mediator.message.type (the sent message's runtime type).
MediatorOptions does not expose a "send failed" event, so if a participant throws, Sent never fires: that send is counted in mediator.sends.started but not in mediator.sends.completed, and does not contribute a mediator.send.duration measurement. No state leaks in that case — pending timing data is correlated against the caller's SendEventArgs instance via a ConditionalWeakTable, so it is garbage-collected along with it rather than accumulating.
- Every
IMediator.SendAsynccall produces anActivitynamed after the message type, parented to whateverActivity.Currentis ambient at the time — a caller's own span, an incoming ASP.NET Core request span, or none. It is tagged withmediator.message.type. - For the same reason as above, a send whose participant throws never has its
Activitystopped, so it will not appear in an exporter. It does not leak: theActivityis reachable only via the sameConditionalWeakTablecorrelation and is collected along with the caller'sSendEventArgs.
MediatorTelemetry.ActivitySource/MediatorTelemetry.Meter– the shared instances used throughout, exposed so you can add your own spans or measurements under the same names.