Table of Contents

OpenTelemetry Integration

CarrotMQ has built-in support for OpenTelemetry. It emits both distributed traces (via Activity) and metrics (via Meter) using the standard OpenTelemetry .NET API, with no additional packages required beyond the standard OpenTelemetry SDK.


Built-in Instrumentation

Signal Name Constant
Metrics CarrotMQ.Meter Names.CarrotMeterName
Traces CarrotMQ.ActivitySource Names.CarrotActivitySourceName

Both constants are defined in CarrotMQ.Core.Telemetry.Names.

CarrotMQ automatically creates spans for publishing and consuming messages, and records metrics such as message counts and processing durations.


Setup

Add OpenTelemetry to your host alongside CarrotMQ. Register the CarrotMQ meter and activity source so the SDK collects them:

using CarrotMQ.Core.Telemetry;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

appBuilder.Services.AddOpenTelemetry()
    .ConfigureResource(r => r.AddService("MyService"))
    .WithMetrics(metrics =>
    {
        metrics.AddMeter(Names.CarrotMeterName);
        metrics.AddRuntimeInstrumentation();
        // Export to OTLP, Prometheus, etc.
    })
    .WithTracing(tracing =>
    {
        tracing.AddSource(Names.CarrotActivitySourceName);
        // Export to Jaeger, Zipkin, OTLP, etc.
    })
    .UseOtlpExporter(); // or your preferred exporter

Tracing Customisation via CarrotTracingOptions

You can enrich CarrotMQ spans with additional tags or baggage using CarrotTracingOptions.

Configuration in Code

builder.ConfigureTracing(configureOptions: options =>
{
    options.EnrichPublishActivityWithHeader = (activity, header) =>
    {
        activity.SetTag("tenant.id", header.CustomHeader?.GetValueOrDefault("X-Tenant-Id"));
    };

    options.EnrichConsumeActivityWithHeader = (activity, header) =>
    {
        activity.SetTag("tenant.id", header.CustomHeader?.GetValueOrDefault("X-Tenant-Id"));
    };
});

Enrichment Hooks

Hook Signature Description
EnrichPublishActivityWithHeader Action<Activity, CarrotHeader> Called when a publish span is started. Add tags or baggage derived from the outgoing message header.
EnrichConsumeActivityWithHeader Action<Activity, CarrotHeader> Called when a consume span is started. Add tags or baggage derived from the incoming message header.

The CarrotHeader Object

The CarrotHeader passed to both hooks exposes:

Property Description
ServiceName Name of the service that originally published the message.
InitialUserName Identity of the user associated with the originating request.
InitialServiceName Name of the service that initiated the original request chain.
CustomHeader IDictionary<string, string>? — arbitrary key/value pairs set via Context.CustomHeader at publish time. May be null if no custom headers were set.

Example: Propagating a Tenant ID

builder.ConfigureTracing(configureOptions: options =>
{
    options.EnrichPublishActivityWithHeader = (activity, header) =>
    {
        activity.SetTag("tenant.id", header.CustomHeader?.GetValueOrDefault("X-Tenant-Id"));
    };
});

Set the custom header when publishing:

// Pass a context with the custom header when publishing
await carrotClient.PublishAsync(
    myEvent,
    context: new Context(customHeader: new Dictionary<string, string>
    {
        ["X-Tenant-Id"] = tenantId
    }));

Trace Flow Diagram

PublisherRabbitMQConsumerPublisher(Service A)Publisher(Service A)RabbitMQBrokerRabbitMQBrokerConsumer(Service B)Consumer(Service B)PublishAsync()[Span: publish]EnrichPublishActivityWithHeadercalled hereDeliver messageHandleAsync()[Span: consume]EnrichConsumeActivityWithHeadercalled here

Meter Instruments Reference

All instruments are published under the meter named CarrotMQ.Meter (Names.CarrotMeterName).

Metrics

Instrument Name Type Unit Description Tags
carrotmq-request-duration-ms Histogram<double> milliseconds Distribution of message processing durations from delivery to final acknowledgement.
carrotmq-active-requests-counter UpDownCounter<long> count Number of messages currently being processed. Incremented when processing starts, decremented when it ends.
carrotmq-request-counter Counter<long> count Total number of messages received and dispatched to a handler. CalledMethod
carrotmq-response-counter Counter<long> count Total number of responses sent back to callers. CalledMethod, StatusCode
carrotmq-message-delivery-counter Counter<long> count Total number of messages acknowledged, rejected, or retried. DeliveryStatus

Activity (Span) Names and Tags

All spans belong to the activity source named CarrotMQ.ActivitySource (Names.CarrotActivitySourceName).

Scenario Activity Kind Activity Name Tags Set
Publishing a message ActivityKind.Producer header.CalledMethod (the message type key) carrotmq.service_name, carrotmq.v_host
Consuming a message ActivityKind.Consumer header.CalledMethod (the message type key) carrotmq.remote_service_name, carrotmq.service_name, carrotmq.v_host

Trace context (TraceId, SpanId) is propagated via the CarrotHeader embedded in each message, enabling correlation across service boundaries without any additional configuration.