Repository navigation
Trace
DotNetWorkQueue uses System.Diagnostics.ActivitySource for tracing, which plugs into OpenTelemetry. All activities come from a single named source you subscribe to in your telemetry pipeline.
ActivitySource name: dotnetworkqueue.instrumentationlibrary
You need the OpenTelemetry hosting extension and an exporter of your choice:
OpenTelemetry.Extensions.Hosting
OpenTelemetry.Exporter.Jaeger (or any other exporter)
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource("dotnetworkqueue.instrumentationlibrary")
.AddJaegerExporter());using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("dotnetworkqueue.instrumentationlibrary")
.AddJaegerExporter()
.Build();Keep the tracerProvider instance alive for the lifetime of your application. Disposing it flushes and shuts down the exporter.
Trace context flows from the producer to the consumer automatically through message headers. When a producer enqueues a message, it captures the current Activity context and stores it in the message. The consumer restores that context before it runs your handler. Thus the full producer-to-consumer span chain shows up in your tracing backend without any extra configuration.
DotNetWorkQueue emits these activities. It emits all of them under the dotnetworkqueue.instrumentationlibrary source.
| Activity | Description |
|---|---|
ReceiveMessage |
Dequeue of a message from the transport |
MessageHandler |
Synchronous user handler execution |
MessageHandlerAsync |
Async user handler execution |
LinqExecution |
LINQ handler execution |
Commit |
Message commit after successful handler |
RollBack |
Message rollback |
Remove |
Message removal from the queue |
Error |
Message moved to error state |
PoisonMessage |
Poison message handling |
ResetHeartBeat |
Stale heartbeat reset |
SendHeartBeat |
Heartbeat update during processing |
SendJobAsync |
Scheduler job enqueue |
MessageSerializerMessageToBytes{Name} |
Serialization (name reflects the serializer) |
MessageSerializerBytesToMessage{Name} |
Deserialization (name reflects the serializer) |
MessageInterceptorMessageToBytes{Name} |
Interceptor encode (name reflects the interceptor) |
MessageInterceptorBytesToMessage{Name} |
Interceptor decode (name reflects the interceptor) |
SendMessage |
Message send (Memory transport) |
SchedulerMessageHandler |
Scheduler handler execution |
At runtime, the library replaces the {Name} suffix on serializer and interceptor activities with the display name of the specific serializer or interceptor implementation in use (e.g., MessageSerializerMessageToBytesJsonSerializer, MessageInterceptorMessageToBytesGZip).
The library tags every activity with context metadata:
| Tag | Source | Notes |
|---|---|---|
Server |
IConnectionInformation.Server |
Transport server/host |
Queue |
IConnectionInformation.QueueName |
Queue name |
CorrelationId |
Message headers | Cross-system correlation |
Route |
Message route | Only present when routing is enabled |
MessageId |
Queue message ID | Added by message-specific activities |
When you send a message, you can also attach custom tags through IAdditionalMessageData.TraceTags. The library propagates these tags to all downstream activities for that message.
Inside a consumer handler, IWorkerNotification exposes the queue's ActivitySource through the Tracer property. Use it to create child spans. The library automatically makes the queue's current activity their parent:
consumer.Start<MyMessage>((message, notification) =>
{
using var activity = notification.Tracer.StartActivity("ProcessOrder");
activity?.SetTag("orderId", message.Body.OrderId);
// your processing logic
}, notifications);These child spans appear in your tracing backend as children of the MessageHandler or MessageHandlerAsync span.
For any issues please use the GitHub issues