Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ac471d9
Implement automatic instrumentation for OpenTelemetry Baggage, so tha…
zacharycmontoya Dec 2, 2025
d496e9f
Fix OpenTelemetry baggage interop. When the user calls the static Ope…
zacharycmontoya Feb 3, 2026
b52ef06
Add support for remaining static API methods:
zacharycmontoya Feb 4, 2026
3cda30c
Add snapshot tests for new AspNetCoreMinimalApisTests.OtelBaggageApiI…
zacharycmontoya Feb 5, 2026
6dc4e28
Optimization 1: In static OpenTelemetry.Baggage API calls, only updat…
zacharycmontoya Feb 5, 2026
61edb29
Update SetCurrent comments
zacharycmontoya Feb 5, 2026
7d6fce8
Merge branch 'master' into zach.montoya/otel-baggage-sync-APMAPI-1736
zacharycmontoya Feb 19, 2026
7993bf7
PR Feedback: Remove unnecessary code comments on OnMethodBegin / OnMe…
zacharycmontoya Mar 10, 2026
acfc98d
PR Feedback: Inline the IntegrationName into the InstrumentMethod att…
zacharycmontoya Mar 10, 2026
ae367aa
PR Feedback: Add 'exception is null' conditional to all of the OnMeth…
zacharycmontoya Mar 10, 2026
e9ef733
PR Feedback: Add links to the baggage implementation types
zacharycmontoya Mar 10, 2026
7a603e1
PR Feedback: Refactor OnMethodBegin methods to all use the 'TBaggage …
zacharycmontoya Mar 10, 2026
5d067d8
PR Feedback: Simplify duck typing for OpenTelemetry.Baggage.get_Curre…
zacharycmontoya Mar 10, 2026
4cf6920
Update OTel Baggage API integration to test to omit the 'otel-baggage…
zacharycmontoya Mar 11, 2026
6513124
Make the OTelBaggage_SetCurrentIntegration, more resilient by only up…
zacharycmontoya Mar 12, 2026
af30626
Add Datadog.Trace.Baggage.AsDictionary() method for getting the exist…
zacharycmontoya Mar 12, 2026
64ea7e4
Fix issue in TypeNameTests
zacharycmontoya Mar 12, 2026
f8b5443
Merge branch 'master' into zach.montoya/otel-baggage-sync-APMAPI-1736
zacharycmontoya Mar 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions tracer/build/supported_calltargets.g.json
Original file line number Diff line number Diff line change
Expand Up @@ -13771,6 +13771,152 @@
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenTelemetry",
"AssemblyName": "OpenTelemetry.Api",
"TargetTypeName": "OpenTelemetry.Baggage",
"TargetMethodName": "ClearBaggage",
"TargetReturnType": "OpenTelemetry.Baggage",
"TargetParameterTypes": [
"OpenTelemetry.Baggage"
],
"MinimumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"MaximumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry.OTelBaggage_ClearBaggageIntegration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenTelemetry",
"AssemblyName": "OpenTelemetry.Api",
"TargetTypeName": "OpenTelemetry.Baggage",
"TargetMethodName": "get_Current",
"TargetReturnType": "OpenTelemetry.Baggage",
"TargetParameterTypes": [],
"MinimumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"MaximumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry.OTelBaggage_GetCurrentIntegration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenTelemetry",
"AssemblyName": "OpenTelemetry.Api",
"TargetTypeName": "OpenTelemetry.Baggage",
"TargetMethodName": "RemoveBaggage",
"TargetReturnType": "OpenTelemetry.Baggage",
"TargetParameterTypes": [
"System.String",
"OpenTelemetry.Baggage"
],
"MinimumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"MaximumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry.OTelBaggage_RemoveBaggageIntegration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenTelemetry",
"AssemblyName": "OpenTelemetry.Api",
"TargetTypeName": "OpenTelemetry.Baggage",
"TargetMethodName": "set_Current",
"TargetReturnType": "System.Void",
"TargetParameterTypes": [
"OpenTelemetry.Baggage"
],
"MinimumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"MaximumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry.OTelBaggage_SetCurrentIntegration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenTelemetry",
"AssemblyName": "OpenTelemetry.Api",
"TargetTypeName": "OpenTelemetry.Baggage",
"TargetMethodName": "SetBaggage",
"TargetReturnType": "OpenTelemetry.Baggage",
"TargetParameterTypes": [
"System.String",
"System.String",
"OpenTelemetry.Baggage"
],
"MinimumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"MaximumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry.OTelBaggage_SetBaggageIntegration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenTelemetry",
"AssemblyName": "OpenTelemetry.Api",
"TargetTypeName": "OpenTelemetry.Baggage",
"TargetMethodName": "SetBaggage",
"TargetReturnType": "OpenTelemetry.Baggage",
"TargetParameterTypes": [
"System.Collections.Generic.IEnumerable`1[System.Collections.Generic.KeyValuePair`2[System.String,System.String]]",
"OpenTelemetry.Baggage"
],
"MinimumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"MaximumVersion": {
"Item1": 1,
"Item2": 0,
"Item3": 0
},
"InstrumentationTypeName": "Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry.OTelBaggage_SetBaggageItemsIntegration",
"IntegrationKind": 0,
"IsAdoNetIntegration": false,
"InstrumentationCategory": 1
},
{
"IntegrationName": "OpenTelemetry",
"AssemblyName": "OpenTelemetry.Api",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// <copyright file="IApiBaggage.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System.Collections.Generic;
using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry
{
/// <summary>
/// OpenTelemetry.Baggage interface for duck-typing
Comment thread
zacharycmontoya marked this conversation as resolved.
/// https://github.com/open-telemetry/opentelemetry-dotnet/blob/db429bf642c1a2c2f71b49f88d63e0a661018298/src/OpenTelemetry.Api/Baggage.cs#L16
/// </summary>
internal interface IApiBaggage
{
[DuckField(Name = "baggage")]
Dictionary<string, string> Baggage { get; }

IApiBaggage Create(Dictionary<string, string?>? baggageItems);

IReadOnlyDictionary<string, string?> GetBaggage();

string? GetBaggage(string name);

IBaggageHolder EnsureBaggageHolder();
}

/// <summary>
/// Baggage holder interface for duck-typing
/// https://github.com/open-telemetry/opentelemetry-dotnet/blob/db429bf642c1a2c2f71b49f88d63e0a661018298/src/OpenTelemetry.Api/Baggage.cs#L371
/// </summary>
internal interface IBaggageHolder
{
[DuckField(Name = "Baggage")]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OMG the casing inconsistency in this code base 😅

IApiBaggage Baggage { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// <copyright file="OTelBaggage_ClearBaggageIntegration.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System;
using System.ComponentModel;
using Datadog.Trace;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;
using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry
{
/// <summary>
/// OpenTelemetry.Api Baggage.ClearBaggage calltarget instrumentation
/// </summary>
[InstrumentMethod(
AssemblyName = "OpenTelemetry.Api",
TypeName = "OpenTelemetry.Baggage",
MethodName = "ClearBaggage",
ReturnTypeName = "OpenTelemetry.Baggage",
ParameterTypeNames = new[] { "OpenTelemetry.Baggage" },
MinimumVersion = "1.0.0",
MaximumVersion = "1.0.0",
Comment thread
andrewlock marked this conversation as resolved.
IntegrationName = nameof(Configuration.IntegrationId.OpenTelemetry))]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OTelBaggage_ClearBaggageIntegration
{
internal const IntegrationId IntegrationId = Configuration.IntegrationId.OpenTelemetry;

internal static CallTargetReturn<TReturn> OnMethodEnd<TTarget, TReturn>(TTarget instance, TReturn returnValue, Exception exception, in CallTargetState state)
{
if (Tracer.Instance.CurrentTraceSettings.Settings.IsIntegrationEnabled(IntegrationId)
&& exception is null)
{
// Important: Before returning to the caller, the static OpenTelemetry.Baggage APIs set the to-be-returned baggage object as OpenTelemetry.Baggage.Current.
// However, this is not done through the public setter (which we instrument), but through the backing field, so we must manually update Datadog.Trace.Baggage.Current
// so it remains in-sync.
Baggage.Current.Clear();
Comment thread
andrewlock marked this conversation as resolved.
}

return new CallTargetReturn<TReturn>(returnValue);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// <copyright file="OTelBaggage_GetCurrentIntegration.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System;
using System.ComponentModel;
using System.Linq;
using Datadog.Trace;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;
using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry
{
/// <summary>
/// OpenTelemetry.Api Baggage.get_Current calltarget instrumentation
/// </summary>
[InstrumentMethod(
AssemblyName = "OpenTelemetry.Api",
TypeName = "OpenTelemetry.Baggage",
MethodName = "get_Current",
ReturnTypeName = "OpenTelemetry.Baggage",
ParameterTypeNames = new string[0],
MinimumVersion = "1.0.0",
MaximumVersion = "1.0.0",
IntegrationName = nameof(Configuration.IntegrationId.OpenTelemetry))]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OTelBaggage_GetCurrentIntegration
{
internal const IntegrationId IntegrationId = Configuration.IntegrationId.OpenTelemetry;

internal static CallTargetState OnMethodBegin<TInstance>(TInstance apiBaggage)
where TInstance : IApiBaggage
{
if (Tracer.Instance.CurrentTraceSettings.Settings.IsIntegrationEnabled(IntegrationId))
{
// Since Datadog.Trace.Baggage.Current may have been updated since the last time OpenTelemetry.Baggage.Current was accessed,
// we must update the underlying OpenTelemetry.Baggage.Current store with the latest Datadog.Trace.Baggage.Current items.
// Note: When the user sets OpenTelemetry.Baggage.Current, those changes will override the contents of Datadog.Trace.Baggage.Current,
// so we can always consider Datadog.Trace.Baggage.Current as being up-to-date.
var baggageHolder = apiBaggage.EnsureBaggageHolder();
baggageHolder.Baggage = apiBaggage.Create(Baggage.Current.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
Comment thread
zacharycmontoya marked this conversation as resolved.
Outdated
}

return CallTargetState.GetDefault();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// <copyright file="OTelBaggage_RemoveBaggageIntegration.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

#nullable enable

using System;
using System.ComponentModel;
using System.Linq;
using Datadog.Trace;
using Datadog.Trace.ClrProfiler.CallTarget;
using Datadog.Trace.Configuration;
using Datadog.Trace.DuckTyping;

namespace Datadog.Trace.ClrProfiler.AutoInstrumentation.OpenTelemetry
{
/// <summary>
/// OpenTelemetry.Api Baggage.RemoveBaggage calltarget instrumentation
/// </summary>
[InstrumentMethod(
AssemblyName = "OpenTelemetry.Api",
TypeName = "OpenTelemetry.Baggage",
MethodName = "RemoveBaggage",
ReturnTypeName = "OpenTelemetry.Baggage",
ParameterTypeNames = new[] { ClrNames.String, "OpenTelemetry.Baggage" },
MinimumVersion = "1.0.0",
MaximumVersion = "1.0.0",
IntegrationName = nameof(Configuration.IntegrationId.OpenTelemetry))]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public sealed class OTelBaggage_RemoveBaggageIntegration
{
internal const IntegrationId IntegrationId = Configuration.IntegrationId.OpenTelemetry;

internal static CallTargetState OnMethodBegin<TInstance, TBaggage>(TInstance instance, string key, TBaggage apiBaggage)
where TBaggage : IApiBaggage
{
// If the user provides the default baggage instance, then OpenTelemetry.Baggage.Current will be used as the baggage source,
// so we must update the underlying OpenTelemetry.Baggage.Current store to the latest Datadog.Trace.Baggage.Current items.
if (Tracer.Instance.CurrentTraceSettings.Settings.IsIntegrationEnabled(IntegrationId)
&& apiBaggage.Baggage is null)
{
// Since Datadog.Trace.Baggage.Current may have been updated since the last time OpenTelemetry.Baggage.Current was accessed,
// we must update the underlying OpenTelemetry.Baggage.Current store with the latest Datadog.Trace.Baggage.Current items.
// Note: When the user sets OpenTelemetry.Baggage.Current, those changes will override the contents of Datadog.Trace.Baggage.Current,
// so we can always consider Datadog.Trace.Baggage.Current as being up-to-date.
var baggageHolder = apiBaggage.EnsureBaggageHolder();
baggageHolder.Baggage = apiBaggage.Create(Baggage.Current.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the OTel APIs treat baggage in a case-insensitive manner, what happens if we set the same key here? Are the duplicate keys overwritten, or does it throw?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No exception, the entry just gets overwritten. But I'm resolving this with open-telemetry/opentelemetry-dotnet#6931

}

return CallTargetState.GetDefault();
}

internal static CallTargetReturn<TReturn> OnMethodEnd<TTarget, TReturn>(TTarget instance, TReturn returnValue, Exception exception, in CallTargetState state)
{
if (Tracer.Instance.CurrentTraceSettings.Settings.IsIntegrationEnabled(IntegrationId)
&& exception is null
&& returnValue.TryDuckCast<IApiBaggage>(out var apiBaggage))
{
// Important: Before returning to the caller, the static OpenTelemetry.Baggage APIs set the to-be-returned baggage object as OpenTelemetry.Baggage.Current.
// However, this is not done through the public setter (which we instrument), but through the backing field, so we must manually update Datadog.Trace.Baggage.Current
// so it remains in-sync.
//
// Additional notes:
// - Since the Datadog Baggage model is mutable (allowing the user to get the Datadog.Trace.Baggage.Current once and continue to mutate that reference),
// we must clear then add the new baggage items.
// - The API can be invoked with an arbitrary OpenTelemetry.Baggage object passed via the parameter, so we must replace all baggage items every time

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, if the user calls RemoveBaggage("mykey", someBaggage), and passes in a baggage object, that (updated) baggage is set to be the new Baggage.Current value, correct? i.e. all of the SetBaggage and RemoveBaggage() APIs always manipulate the Current baggage, passing in an explicit baggage object just manipulates the "starting" point?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Haha.

For the static methods, the baggage update will take place on the baggage object passed in or Baggage.Current if nothing is passed in. Then the resulting baggage is set as the new value of Baggage.Current.

// we perform this instrumentation.
Baggage.Current.Clear();
var newBaggage = new Baggage(apiBaggage.GetBaggage());
newBaggage.MergeInto(Baggage.Current);
}

return new CallTargetReturn<TReturn>(returnValue);
}
}
}
Loading
Loading