Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions manifests/cpp_httpd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ tests/:
Test_MultipartUpload: missing_feature
Test_PutObject: missing_feature
test_baggage.py:
Test_Baggage_Headers_Api_Datadog: incomplete_test_app (/otel_drop_in_baggage_api_datadog endpoint is not implemented)
Test_Baggage_Headers_Api_OTel: incomplete_test_app (/otel_drop_in_baggage_api_otel endpoint is not implemented)
Test_Baggage_Headers_Basic: incomplete_test_app (/make_distant_call endpoint is not implemented)
Test_Baggage_Headers_Malformed: incomplete_test_app (/make_distant_call endpoint is not implemented)
Test_Baggage_Headers_Malformed2: incomplete_test_app (/make_distant_call endpoint is not implemented)
Expand Down
2 changes: 2 additions & 0 deletions manifests/cpp_nginx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ tests/:
Test_MultipartUpload: missing_feature
Test_PutObject: missing_feature
test_baggage.py:
Test_Baggage_Headers_Api_Datadog: incomplete_test_app (/otel_drop_in_baggage_api_datadog endpoint is not implemented)
Test_Baggage_Headers_Api_OTel: incomplete_test_app (/otel_drop_in_baggage_api_otel endpoint is not implemented)
Test_Baggage_Headers_Basic: incomplete_test_app (/make_distant_call endpoint is not implemented)
Test_Baggage_Headers_Malformed: incomplete_test_app (/make_distant_call endpoint is not implemented)
Test_Baggage_Headers_Malformed2: incomplete_test_app (/make_distant_call endpoint is not implemented)
Expand Down
2 changes: 2 additions & 0 deletions manifests/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,8 @@ tests/:
Test_MultipartUpload: missing_feature
Test_PutObject: missing_feature
test_baggage.py:
Test_Baggage_Headers_Api_Datadog: v3.6.0
Test_Baggage_Headers_Api_OTel: missing_feature (OTel baggage not unified with Datadog baggage)
Test_Baggage_Headers_Basic: v3.6.0
Test_Baggage_Headers_Malformed: v3.6.0
Test_Baggage_Headers_Malformed2: v3.6.0
Expand Down
2 changes: 2 additions & 0 deletions manifests/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,8 @@ tests/:
Test_MultipartUpload: missing_feature
Test_PutObject: missing_feature
test_baggage.py:
Test_Baggage_Headers_Api_Datadog: incomplete_test_app (/otel_drop_in_baggage_api_datadog endpoint is not implemented)
Test_Baggage_Headers_Api_OTel: incomplete_test_app (/otel_drop_in_baggage_api_otel endpoint is not implemented)
Test_Baggage_Headers_Basic: incomplete_test_app (/make_distant_call endpoint is not correctly implemented)
Test_Baggage_Headers_Malformed: v2.2.3
Test_Baggage_Headers_Malformed2: v2.2.3
Expand Down
10 changes: 10 additions & 0 deletions manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2202,6 +2202,16 @@ tests/:
Test_Mock: v0.2.1
Test_NotReleased: missing_feature
test_baggage.py:
Test_Baggage_Headers_Api_Datadog: irrelevant (no Datadog API for W3C Baggage)
Test_Baggage_Headers_Api_OTel:
'*': missing_feature (OTel baggage not unified with Datadog baggage)
akka-http: incomplete_test_app (endpoint does not support fetching headers injected to downstream call)
jersey-grizzly2: incomplete_test_app (endpoint does not support fetching headers injected to downstream call)
play: incomplete_test_app (endpoint does not support fetching headers injected to downstream call)
ratpack: incomplete_test_app (endpoint does not support fetching headers injected to downstream call)
resteasy-netty3: incomplete_test_app (endpoint does not support fetching headers injected to downstream call)
spring-boot: missing_feature (OTel baggage not unified with Datadog baggage)
spring-boot-3-native: incomplete_test_app (endpoint does not support fetching headers injected to downstream call)
Test_Baggage_Headers_Basic:
'*': v1.53.0
akka-http: incomplete_test_app (endpoint does not support fetching headers injected to downstream call)
Expand Down
8 changes: 8 additions & 0 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,14 @@ tests/:
Test_MultipartUpload: missing_feature
Test_PutObject: missing_feature
test_baggage.py:
Test_Baggage_Headers_Api_Datadog:
'*': *ref_5_56_0
express4-typescript: incomplete_test_app (endpoint not implemented)
nextjs: incomplete_test_app (endpoint not implemented)
Test_Baggage_Headers_Api_OTel:
'*': missing_feature (OTel baggage not unified with Datadog baggage)
express4-typescript: incomplete_test_app (endpoint not implemented)
nextjs: incomplete_test_app (endpoint not implemented)
Test_Baggage_Headers_Basic: *ref_5_56_0
Test_Baggage_Headers_Malformed: *ref_5_56_0
Test_Baggage_Headers_Malformed2: *ref_5_56_0
Expand Down
2 changes: 2 additions & 0 deletions manifests/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,8 @@ tests/:
Test_MultipartUpload: missing_feature
Test_PutObject: missing_feature
test_baggage.py:
Test_Baggage_Headers_Api_Datadog: incomplete_test_app (/otel_drop_in_baggage_api_datadog endpoint is not implemented)
Test_Baggage_Headers_Api_OTel: incomplete_test_app (/otel_drop_in_baggage_api_otel endpoint is not implemented)
Test_Baggage_Headers_Basic: incomplete_test_app (/make_distant_call endpoint is not correctly implemented)
Test_Baggage_Headers_Malformed: incomplete_test_app (/make_distant_call endpoint is not correctly implemented)
Test_Baggage_Headers_Malformed2: incomplete_test_app (/make_distant_call endpoint is not correctly implemented)
Expand Down
6 changes: 6 additions & 0 deletions manifests/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,12 @@ tests/:
flask-poc: v2.14.0
python3.12: v2.14.0
test_baggage.py:
Test_Baggage_Headers_Api_Datadog:
'*': incomplete_test_app (endpoint not implemented)
flask-poc: v2.16.0
Test_Baggage_Headers_Api_OTel:
'*': incomplete_test_app (endpoint not implemented)
flask-poc: missing_feature (OTel baggage not unified with Datadog baggage)
Test_Baggage_Headers_Basic:
'*': irrelevant
fastapi: v2.16.0
Expand Down
6 changes: 6 additions & 0 deletions manifests/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,12 @@ tests/:
Test_MultipartUpload: missing_feature
Test_PutObject: missing_feature
test_baggage.py:
Test_Baggage_Headers_Api_Datadog:
"*": incomplete_test_app (endpoint not implemented)
rails72: missing_feature (FrozenError (can't modify frozen Hash) from baggage headers)
Test_Baggage_Headers_Api_OTel:
"*": incomplete_test_app (endpoint not implemented)
rails72: missing_feature (FrozenError (can't modify frozen Hash) from baggage headers)
Test_Baggage_Headers_Basic: v2.13.0
Test_Baggage_Headers_Malformed: v2.13.0
Test_Baggage_Headers_Malformed2: v2.13.0
Expand Down
74 changes: 74 additions & 0 deletions tests/test_baggage.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,77 @@ def test_max_bytes(self):
assert len(items) == 1
header_size = len(header_str.encode("utf-8"))
assert header_size <= self.max_bytes


@scenarios.tracing_config_empty
@features.datadog_baggage_headers
class Test_Baggage_Headers_Api_OTel:
def setup_otel_api_update(self):
self.r = weblog.get(
"/otel_drop_in_baggage_api_otel",
params={
"url": "http://weblog:7777",
"baggage_set": "foo=overwrite_value,new_foo=new_value",
"baggage_remove": "remove_me_key",
},
headers={
"x-datadog-parent-id": "10",
"x-datadog-trace-id": "2",
"baggage": "foo=value_to_be_replaced,FOO=UNTOUCHED,remove_me_key=remove_me_value",
},
)

def test_otel_api_update(self):
interfaces.library.assert_trace_exists(self.r)
assert self.r.status_code == 200
data = json.loads(self.r.text)
baggage_header_value = extract_baggage_value(data["request_headers"])
assert baggage_header_value is not None
header_str = baggage_header_value[0] if isinstance(baggage_header_value, list) else baggage_header_value
items = header_str.split(",")

# Expect the following baggage items:
# - "foo=overwrite_value (new pair with conflicting case-sensitive key replaces old pair)
# - "FOO=UNTOUCHED (keys are case-sensitive, so it does not get replaced)
# - "new_foo=new_value (new pair added)
assert len(items) == 3
assert "foo=overwrite_value" in items
assert "FOO=UNTOUCHED" in items
assert "new_foo=new_value" in items


@scenarios.tracing_config_empty
@features.datadog_baggage_headers
class Test_Baggage_Headers_Api_Datadog:
def setup_datadog_api_update(self):
self.r = weblog.get(
"/otel_drop_in_baggage_api_datadog",
params={
"url": "http://weblog:7777",
"baggage_set": "foo=overwrite_value,new_foo=new_value",
"baggage_remove": "remove_me_key",
},
headers={
"x-datadog-parent-id": "10",
"x-datadog-trace-id": "2",
"baggage": "foo=value_to_be_replaced,FOO=UNTOUCHED,remove_me_key=remove_me_value",
},
)

def test_datadog_api_update(self):
interfaces.library.assert_trace_exists(self.r)
assert self.r.status_code == 200
data = json.loads(self.r.text)
baggage_header_value = extract_baggage_value(data["request_headers"])
assert baggage_header_value is not None
header_str = baggage_header_value[0] if isinstance(baggage_header_value, list) else baggage_header_value
items = header_str.split(",")

# Expect the following baggage items:
# - "foo=overwrite_value (new pair with conflicting case-sensitive key replaces old pair)
# - "FOO=BAR (keys are case-sensitive, so it does not get replaced)
# - "new_foo=new_value (new pair added)
assert len(items) == 3
assert "foo=overwrite_value" in items
assert "FOO=UNTOUCHED" in items
assert "new_foo=new_value" in items
110 changes: 110 additions & 0 deletions utils/build/docker/dotnet/weblog/Endpoints/OtelDropInEndpoint.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using OpenTelemetry;
Expand All @@ -11,6 +13,38 @@ namespace weblog
{
public class OtelDropInEndpoint : ISystemTestEndpoint
{
private class BaggageApiEndpointParameters
{
public string? Url { get; private init; }
public string? BaggageToRemove { get; private init; }
public string? BaggageToSet { get; private init; }
public static BaggageApiEndpointParameters Bind(HttpContext context)
{
string? url = context.Request.Query["url"];
string? baggageToRemove = context.Request.Query["baggage_remove"];
string? baggageToSet = context.Request.Query["baggage_set"];
var result = new BaggageApiEndpointParameters
{
Url = url,
BaggageToRemove = baggageToRemove,
BaggageToSet = baggageToSet,
};
return result;
}
}

private class BaggageApiEndpointResponse
{
[JsonPropertyName("url")]
public string? Url { get; set; }
[JsonPropertyName("status_code")]
public int StatusCode { get; set; }
[JsonPropertyName("request_headers")]
public Dictionary<string, string>? RequestHeaders { get; set; }
[JsonPropertyName("response_headers")]
public Dictionary<string, string>? ResponseHeaders { get; set; }
}

public void Register(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder routeBuilder)
{
routeBuilder.MapGet("/otel_drop_in_default_propagator_extract", async context =>
Expand Down Expand Up @@ -44,6 +78,82 @@ public void Register(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder routeBui

await context.Response.WriteAsync(JsonSerializer.Serialize(headersDict));
});

routeBuilder.MapGet("/otel_drop_in_baggage_api_otel", async context =>
{
var parameters = BaggageApiEndpointParameters.Bind(context);
if (parameters.Url == null)
{
var example = "http://localhost:7777/make_distant_call?url=http%3A%2F%2Fweblog%3A7777";
throw new System.Exception($"Specify the url to call in the query string: {example}");
}

if (parameters.BaggageToRemove is not null)
{
foreach (var item in parameters.BaggageToRemove.Split(','))
{
OpenTelemetry.Baggage.RemoveBaggage(item.Trim());
}
}

if (parameters.BaggageToSet is not null)
{
foreach (var item in parameters.BaggageToSet.Split(','))
{
var keyValue = item.Split('=');
OpenTelemetry.Baggage.SetBaggage(keyValue[0].Trim(), keyValue[1].Trim());
}
}

var response = await HttpClientWrapper.LocalGetRequest(parameters.Url);
var endpointResponse = new BaggageApiEndpointResponse()
{
Url = parameters.Url,
StatusCode = (int)response.StatusCode,
RequestHeaders = response.RequestMessage?.Headers.Select(kvp => new KeyValuePair<string, string>(kvp.Key, kvp.Value.First())).ToDictionary(),
ResponseHeaders = response.Headers.Select(kvp => new KeyValuePair<string, string>(kvp.Key, kvp.Value.First())).ToDictionary(),
};

await context.Response.WriteAsJsonAsync(endpointResponse);
});

routeBuilder.MapGet("/otel_drop_in_baggage_api_datadog", async context =>
{
var parameters = BaggageApiEndpointParameters.Bind(context);
if (parameters.Url == null)
{
var example = "http://localhost:7777/make_distant_call?url=http%3A%2F%2Fweblog%3A7777";
throw new System.Exception($"Specify the url to call in the query string: {example}");
}

if (parameters.BaggageToRemove is not null)
{
foreach (var item in parameters.BaggageToRemove.Split(','))
{
Datadog.Trace.Baggage.Current.Remove(item.Trim());
}
}

if (parameters.BaggageToSet is not null)
{
foreach (var item in parameters.BaggageToSet.Split(','))
{
var keyValue = item.Split('=');
Datadog.Trace.Baggage.Current[keyValue[0].Trim()] = keyValue[1].Trim();
}
}

var response = await HttpClientWrapper.LocalGetRequest(parameters.Url);
var endpointResponse = new BaggageApiEndpointResponse()
{
Url = parameters.Url,
StatusCode = (int)response.StatusCode,
RequestHeaders = response.RequestMessage?.Headers.Select(kvp => new KeyValuePair<string, string>(kvp.Key, kvp.Value.First())).ToDictionary(),
ResponseHeaders = response.Headers.Select(kvp => new KeyValuePair<string, string>(kvp.Key, kvp.Value.First())).ToDictionary(),
};

await context.Response.WriteAsJsonAsync(endpointResponse);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@


import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.baggage.BaggageBuilder;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
Expand Down Expand Up @@ -1320,6 +1321,67 @@ public void set(Map<String, String> map, String key, String value) {
return jsonString;
}

@RequestMapping("/otel_drop_in_baggage_api_otel")
DistantCallResponse otelDropInBaggageApiOTel(@RequestParam String url, @RequestParam String baggage_remove, @RequestParam String baggage_set) throws Exception {
Copy link
Contributor

@mcculls mcculls Nov 12, 2025

Choose a reason for hiding this comment

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

Tip: if you want to use camel-case in the Java code (or tweak the name in any way) you can add the parameter's snake-case name to @RequestParam:

Suggested change
DistantCallResponse otelDropInBaggageApiOTel(@RequestParam String url, @RequestParam String baggage_remove, @RequestParam String baggage_set) throws Exception {
DistantCallResponse otelDropInBaggageApiOTel(@RequestParam("url") String url, @RequestParam("baggage_remove") String baggageToRemove, @RequestParam("baggage_set") String baggageToSet) throws Exception {

In this case though you're also relying on the automatic mapping when serializing DistantCallResponse which uses the same snake case for its fields, so on reflection it's not worth changing this :)

// Insert baggage operations here
BaggageBuilder baggageBuilder = Baggage.current().toBuilder();

// for each
if (baggage_remove != null) {
for (String key : baggage_remove.split(",")) {
baggageBuilder = baggageBuilder.remove(key.trim());
}
}

if (baggage_set != null) {
for (String key : baggage_set.split(",")) {
String[] keyValue = key.split("=");
baggageBuilder =baggageBuilder.put(keyValue[0].trim(), keyValue[1].trim());
}
}

Baggage newBaggage = baggageBuilder.build();
try (Scope scope = newBaggage.makeCurrent()) {
HashMap<String, String> request_headers = new HashMap<>();

OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(chain -> { // Save request headers
Request request = chain.request();
Response response = chain.proceed(request);
Headers finalHeaders = request.headers();
for (String name : finalHeaders.names()) {
request_headers.put(name, finalHeaders.get(name));
}

return response;
})
.build();

Request request = new Request.Builder()
.url(url)
.get()
.build();

Response response = client.newCall(request).execute();

// Save response headers and status code
int status_code = response.code();
HashMap<String, String> response_headers = new HashMap<String, String>();
Headers headers = response.headers();
for (String name : headers.names()) {
response_headers.put(name, headers.get(name));
}

DistantCallResponse result = new DistantCallResponse();
result.url = url;
result.status_code = status_code;
result.request_headers = request_headers;
result.response_headers = response_headers;

return result;
}
}

@GetMapping(value = "/requestdownstream")
public String requestdownstream(HttpServletResponse response) throws IOException {
String url = "http://localhost:7777/returnheaders";
Expand Down
Loading
Loading