Sitelet https://github.com/open-telemetry/opentelemetry-dotnet/issues/5667
Skip to content

Baggage propagation is not possible for instrumentations without depending on OpenTelemetry.Api package #5667

Description

@lmolkova

Package

OpenTelemetry.Api

Package Version

Package Name Version
OpenTelemetry.Api 1.8.0
OpenTelemetry 1.8.0

Description

In Azure SDKs we want to be able to propagate baggage between messaging producer and consumer.

However there are two baggages:

  • System.Diagnostics.Acitivity.Baggage - legacy(?) API that uses Correlation-Context header (by default via default implementation of DistributedContextPropagator)
  • OpenTelemetry.Baggage.Current - OTel baggage available in OpenTelemetry.Api package.

OTel discourages usage of Activity.Baggage:

OpenTelemetry users should not use the `Activity.AddBaggage` method.

As a result:

  • It's not possible to use OTel baggage without taking dependency on OpenTelemetry.Api - this is a showstopper for Azure SDK instrumentations
  • Users have inconsistent baggages that don't work together

Expected Result

OTel and BCL should provide just one way to work with the baggage.

In the meantime, OTel can still provide consistent experience in the following way:

  1. Implement System.Diagnostics.DistributedContextPropagator that does OTel-compatible baggage propagation: W3C + baggage header. It also reads any context provided in the Activity.Baggage (and never sets it)
  2. Configure that propagator on DistributedContextPropagator.Current - at least as an opt-in mechanism

Here's a PoC (tested for general happy case)

class OTelBridgePropagator : DistributedContextPropagator
{
    private readonly TextMapPropagator _inner;
    public OTelBridgePropagator(TextMapPropagator propagator)
    {
        _inner = propagator;
        Fields = new ReadOnlyCollection<string>(_inner.Fields.ToArray());
    }

    public override IReadOnlyCollection<string> Fields { get; }

    private PropagationContext Extract(object? carrier, PropagatorGetterCallback getter)
    {
        return _inner.Extract(default, carrier, (c, n) =>
        {
            getter.Invoke(c, n, out var value, out var values);
            return values;
        });

    }
    public override IEnumerable<KeyValuePair<string, string?>>? ExtractBaggage(object? carrier, PropagatorGetterCallback? getter)
    {
        if (getter == null)
        {
            return null;
        }

        return Extract(carrier, getter).Baggage.GetBaggage();
    }

    public override void ExtractTraceIdAndState(object? carrier, PropagatorGetterCallback? getter, out string? traceParent, out string? traceState)
    {
        traceParent = null;
        traceState = null;

        if (getter != null)
        {
            var context = Extract(carrier, getter);
            var flags = (context.ActivityContext.TraceFlags == ActivityTraceFlags.Recorded) ? "01" : "00";
            traceParent = $"00-{context.ActivityContext.TraceId}-{context.ActivityContext.SpanId}-{flags}";
            traceState = context.ActivityContext.TraceState;
        }
    }

    public override void Inject(Activity? activity, object? carrier, PropagatorSetterCallback? setter)
    {
        if (setter == null)
        {
            return;
        }

        if (activity != null)
        {
            foreach (var kvp in activity.Baggage)
            {
                Baggage.Current.SetBaggage(kvp.Key, kvp.Value);
            }
        }

        var context = new PropagationContext(activity?.Context ?? default, Baggage.Current);
        _inner.Inject(context, carrier, setter.Invoke);
    }
}

Configuration

var otelPropagator = new CompositeTextMapPropagator(
    new TextMapPropagator[] {
        new TraceContextPropagator(),
        new BaggagePropagator(),
    });
Sdk.SetDefaultTextMapPropagator(otelPropagator); 
DistributedContextPropagator.Current = new OTelBridgePropagator(otelPropagator); // this should be done by OTel SDK

As a result:

  • users can add baggage using Baggage.Current or via Activity.Baggage - both will end up in baggage header (or whatever is configured withSetDefaultTextMapPropagator
  • Client/instrumentation libraries can use DistributedContextPropagator.Current to propagate context - it will be consistent with one in the Sdk.SetDefaultTextMapPropagator

Related issues:

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-runtime-changeIssues which likely require changes from dotnet runtime - typically DiagnosticSource packagepkg:OpenTelemetry.ApiIssues related to OpenTelemetry.Api NuGet package

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions