Skip to content

Developers will be able to control how distributed tracing information is propagated #50658

@shirhatti

Description

@shirhatti

Description edited by @tarekgh to include the API proposal

Background and Motivation

When .NET applications need to send their distributed tracing state to another process, they currently rely on functionality baked into HttpClient to transmit this information via HTTP headers (as described in W3C tracecontext spec and baggage spec). Similarly, when ASP.NET application receive distributed tracing information, they rely on the ASP.NET libraries to populate distributed tracing state based on incoming headers.

Unfortunately, the current implementation requires both libraries (HttpClient and ASP.NET) have prior knowledge of the specification. This makes it hard to react to changes in the specification or support other specifications (e.g., B3, Jaeger).

Propagation is a crossing-cutting concern should be solved in System.Diagnostics.DiagnosticsSource rather than require every library to have special knowledge of the wire protocol involved. Solving this in a generic way will also allow other libraries in the .NET ecosystem to participate in distributed tracing without requiring knowledge of the wire protocols involved.

The propagators are part of OpenTelemetry specification too. Having them in .NET is a good step to support it for libraries not taking direct dependencies on the OpenTelemetry.

This proposal also addressing solving customer scenarios that is listed below in the Customer issues addressed section.

Proposed API

namespace System.Diagnostics
{
    public abstract class TextMapPropagator
    {
      public delegate void PropagatorGetterCallback(object carrier, string fieldName, out string? value, out IEnumerable<string>? values);
      public abstract IReadOnlyCollection<string> Fields { get; }
      public abstract void Inject(Activity activity, object carrier, Action<object, string, string> setter);
      public abstract void Extract(object carrier, PropagatorGetterCallback getter, out string? id, out string? state);
      public abstract void Extract(object carrier, PropagatorGetterCallback getter, out IEnumerable<KeyValuePair<string, string?>>? baggage);
      public static TextMapPropagator Current { get; set; }
      public static TextMapPropagator CreateLegacyPropagator() { throw null; }
      public static TextMapPropagator CreatePassThroughPropagator() { throw null; }
      public static TextMapPropagator CreateNoOutputPropagator() { throw null; }
    }
}

Usage Examples

TextMapPropagator propagator = TextMapPropagator.Current;

propagator.Inject(Activity.Current, httpRequest, (carrier1, name, value1) => Console.WriteLine($"{name}: {value1}"));

propagator.Extract(httpRequest, (carrier2, name, out value, out values) => { Console.WriteLine($"{name}"); value =  "Mapped Key Value"; values = null; } );

Customer issues addressed:

Pass-thru mode

@a-elsheikh, @rynowak

There are certain environments (service meshes, dapr) where you are trying to avoid creating a intermediate span. Creating intermediate spans that aren't exported result in the causal chain being broken. (dotnet/aspnetcore#30392)

Proposed solution:

  • Create Activity with predetermined SpanId (which is equal to the parent's span id). This will require a new API
  • Call ActivityListeners, i.e., no special heuristics to suppress Activity Created callbacks when in pass-thru mode. Though we expect the use case here is that there are no listeners in the process.
  • Open question: How does the library (ASP.NET) know that is in this special pass-thru mode?
    • Is it a new static property on Activity?
    • Or is it a library specific setting?
    • Add a bit to ActivityContext to indicate we're in pass-thru mode
  • This scenario will be broken by uncooperative libraries who create child spans, but we will do nothing special to mitigate this

React to spec changes

@karelz, @MihaZupan, @Tratcher

As an example, the W3C baggage specification changed the name of the header from Correlation-Context to Baggage. We'd need a way to change to version and change propagation behavior without requiring every library to react to spec changes

-#45496
-dotnet/aspnetcore#28319

Turn off propagation

@karelz, @MihaZupan

There are instances where application want to turn off propagation the currently isn't an easy way to do without requiring every library to add library-specific settings. This could be addressed by adding a no-op propagator.

TODO: Add links to HttpClient issues where customers want to disable header propagation

Potential work items:

  • Add Propagator (abstract base class) to System.Diagnostics.DiagnosticSource
  • Add implementation for W3C propagator W3C baggage specs still not finalized. we'll wait to expose this propagator till the specs are final.
  • Add implementation for no-op propagator
  • Add a static DefaultPropagator to Activity the static property will be on the propagator abstract class (i.e. TextMapPropagator) instead of Activity class.
  • Add API to create Activity with predetermined SpanId this will not be needed after we expose the propagator.
  • Result of the open question
  • Modify System.Net.Http.DiagnosticsHandler to use the Default Propagator
  • Modify Microsoft.AspNetCore.Hosting.HostingApplicationDiagnostics to use the Default Propagator

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions