Table of Contents

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)