OpenTelemetry
NATS.Net has built-in distributed tracing support using System.Diagnostics.Activity,
the standard .NET API for OpenTelemetry. Activities are created automatically for publish and subscribe operations,
and trace context is propagated through message headers so that send and receive spans are linked across services.
The activity source name is NATS.Net.
Setting Up Tracing
To collect traces, register a listener for the NATS.Net activity source. You can use the OpenTelemetry SDK
with an exporter (Jaeger, Zipkin, OTLP, etc.) or a plain ActivityListener for lightweight scenarios:
// The NATS.Net client uses System.Diagnostics.Activity for tracing.
// No additional packages are needed to enable tracing — just add an
// ActivityListener or configure an OpenTelemetry TracerProvider that
// listens to the "NATS.Net" source.
// Using OpenTelemetry SDK (install OpenTelemetry and an exporter):
//
// using var tracerProvider = Sdk.CreateTracerProviderBuilder()
// .AddSource("NATS.Net") // listen for NATS activities
// .AddSource("MyApp") // listen for your own activities
// .AddOtlpExporter() // export to Jaeger, Zipkin, etc.
// .Build();
// Or using a plain ActivityListener (no extra packages):
using ActivityListener listener = new ActivityListener
{
ShouldListenTo = source => source.Name == "NATS.Net",
Sample = (ref ActivityCreationOptions<ActivityContext> _) =>
ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = activity =>
Console.WriteLine($"Started: {activity.OperationName}"),
ActivityStopped = activity =>
Console.WriteLine($"Stopped: {activity.OperationName}"),
};
ActivitySource.AddActivityListener(listener);
Automatic Trace Context Propagation
When you publish a message, the client injects the current trace context into the message headers. When a subscriber reads the message, the receive activity is automatically parented to the send activity, giving you end-to-end traces across services with no extra code:
await using NatsConnection nats = new NatsConnection();
// Publish and subscribe — activities are created automatically
await using var sub = await nats.SubscribeCoreAsync<string>("orders.new");
await nats.PublishAsync("orders.new", "order-123");
// The message carries trace context in its headers, so the
// receive activity is automatically linked to the send activity.
await foreach (NatsMsg<string> msg in sub.Msgs.ReadAllAsync())
{
Console.WriteLine($"Received: {msg.Data}");
break;
}
Starting Custom Activities
You can start child activities under a message's trace context using the StartActivity extension method.
This is useful for tracking processing work that happens after a message is received:
await using NatsConnection nats = new NatsConnection();
await using var sub = await nats.SubscribeCoreAsync<string>("work.items");
await nats.PublishAsync("work.items", "item-456");
await foreach (NatsMsg<string> msg in sub.Msgs.ReadAllAsync())
{
// Start a child activity under the message's trace context
using Activity? activity = msg.StartActivity("ProcessWorkItem");
// The activity is linked to the original publish span
Console.WriteLine($"Processing: {msg.Data}");
break;
}
The StartActivity method is available on both NatsMsg<T>
and INatsJSMsg<T> for JetStream messages.
Filtering
Use NatsInstrumentationOptions.Default.Filter to skip
telemetry for specific requests. When the filter returns false, no activity is created:
// Filter lets you skip telemetry for specific subjects
NatsInstrumentationOptions.Default.Filter = context =>
{
// Skip internal/health-check subjects
if (context.Subject.StartsWith("_INBOX."))
return false;
return true;
};
Enriching Activities
Use NatsInstrumentationOptions.Default.Enrich to add
custom tags to every activity:
// Enrich lets you add custom tags to every activity
NatsInstrumentationOptions.Default.Enrich = (activity, context) =>
{
activity.SetTag("app.environment", "production");
if (context.QueueGroup is not null)
activity.SetTag("app.queue_group", context.QueueGroup);
};
Semantic Conventions
NATS.Net follows the OpenTelemetry Semantic Conventions for Messaging. The following attributes are set on activities:
| Attribute | Example | Description |
|---|---|---|
messaging.system |
nats |
Always nats |
messaging.operation |
publish / receive |
Operation type |
messaging.destination.name |
orders.new |
Subject name |
messaging.client_id |
42 |
NATS client ID |
server.address |
localhost |
Server host |
server.port |
4222 |
Server port |
network.protocol.name |
nats |
Protocol name |
network.transport |
tcp |
Transport protocol |
network.peer.address |
localhost |
Remote host |
network.peer.port |
4222 |
Remote port |
network.local.address |
127.0.0.1 |
Local IP |
Receive activities include additional attributes:
| Attribute | Example | Description |
|---|---|---|
messaging.destination.template |
orders.* |
Subscription subject pattern |
messaging.message.body.size |
1024 |
Message body size in bytes |
messaging.message.envelope.size |
1280 |
Total message size in bytes |
messaging.consumer.group.name |
workers |
Queue group (if used) |