diff --git a/manifests/cpp_httpd.yml b/manifests/cpp_httpd.yml index 896ce41badc..7e6e3365d49 100644 --- a/manifests/cpp_httpd.yml +++ b/manifests/cpp_httpd.yml @@ -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) diff --git a/manifests/cpp_nginx.yml b/manifests/cpp_nginx.yml index 6fa0d890cd1..37ef970c51f 100644 --- a/manifests/cpp_nginx.yml +++ b/manifests/cpp_nginx.yml @@ -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) diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index 982dc8b1646..fc3fc5f0bde 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -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 diff --git a/manifests/golang.yml b/manifests/golang.yml index 2845ce1d6b0..fdbc96eadcc 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -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 diff --git a/manifests/java.yml b/manifests/java.yml index 2f2bfc2fb74..3836369533f 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -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) diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index d56ced81c1f..af09b5ea94f 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -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 diff --git a/manifests/php.yml b/manifests/php.yml index 5e21c05c2c0..982dd4f8206 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -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) diff --git a/manifests/python.yml b/manifests/python.yml index d447c671cf1..4ce03100b74 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -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 diff --git a/manifests/ruby.yml b/manifests/ruby.yml index 937a3a81cd9..046c57bc57f 100644 --- a/manifests/ruby.yml +++ b/manifests/ruby.yml @@ -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 diff --git a/tests/test_baggage.py b/tests/test_baggage.py index b80844ee430..1fa23cf718b 100644 --- a/tests/test_baggage.py +++ b/tests/test_baggage.py @@ -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 diff --git a/utils/build/docker/dotnet/weblog/Endpoints/OtelDropInEndpoint.cs b/utils/build/docker/dotnet/weblog/Endpoints/OtelDropInEndpoint.cs index a26b950ec01..cddcc037afe 100644 --- a/utils/build/docker/dotnet/weblog/Endpoints/OtelDropInEndpoint.cs +++ b/utils/build/docker/dotnet/weblog/Endpoints/OtelDropInEndpoint.cs @@ -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; @@ -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? RequestHeaders { get; set; } + [JsonPropertyName("response_headers")] + public Dictionary? ResponseHeaders { get; set; } + } + public void Register(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder routeBuilder) { routeBuilder.MapGet("/otel_drop_in_default_propagator_extract", async context => @@ -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(kvp.Key, kvp.Value.First())).ToDictionary(), + ResponseHeaders = response.Headers.Select(kvp => new KeyValuePair(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(kvp.Key, kvp.Value.First())).ToDictionary(), + ResponseHeaders = response.Headers.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.First())).ToDictionary(), + }; + + await context.Response.WriteAsJsonAsync(endpointResponse); + }); } } } diff --git a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/App.java b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/App.java index 053e8b5cd53..84848aee67f 100644 --- a/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/App.java +++ b/utils/build/docker/java/spring-boot/src/main/java/com/datadoghq/system_tests/springboot/App.java @@ -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; @@ -1320,6 +1321,67 @@ public void set(Map 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 { + // 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 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 response_headers = new HashMap(); + 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"; diff --git a/utils/build/docker/nodejs/fastify/app.js b/utils/build/docker/nodejs/fastify/app.js index 28b3241af80..7612a2c5ec5 100644 --- a/utils/build/docker/nodejs/fastify/app.js +++ b/utils/build/docker/nodejs/fastify/app.js @@ -587,6 +587,131 @@ fastify.get('/otel_drop_in_default_propagator_inject', async (request, reply) => return result }) +fastify.get('/otel_drop_in_baggage_api_otel', async (request, reply) => { + const api = require('@opentelemetry/api') + + const url = request.query.url + console.log(url) + + const parsedUrl = new URL(url) + + const options = { + hostname: parsedUrl.hostname, + port: parsedUrl.port || 80, // Use default port if not provided + path: parsedUrl.pathname, + method: 'GET' + } + + const baggageRemove = request.query.baggage_remove + const baggageSet = request.query.baggage_set + const baggageToRemove = baggageRemove ? baggageRemove.split(',') : [] + const baggageToSet = baggageSet ? baggageSet.split(',').map(item => item.split('=')) : [] + + let baggage = api.propagation.getActiveBaggage() || api.propagation.createBaggage() + for (const key of baggageToRemove) { + baggage = baggage.removeEntry(key.trim()) + } + for (const [key, value] of baggageToSet) { + baggage = baggage.setEntry(key.trim(), { value: value.trim() }) + } + + const newContext = api.propagation.setBaggage(api.context.active(), baggage) + + return new Promise((resolve, reject) => { + api.context.with(newContext, () => { + const httpRequest = http.request(options, (response) => { + let responseBody = '' + response.on('data', (chunk) => { + responseBody += chunk + }) + + response.on('end', () => { + resolve({ + url, + status_code: response.statusCode, + request_headers: response.req._headers, + response_headers: response.headers, + response_body: responseBody + }) + }) + }) + + httpRequest.on('error', (error) => { + console.log(error) + resolve({ + url, + status_code: 500, + request_headers: null, + response_headers: null + }) + }) + + httpRequest.end() + }) + }) +}) + +fastify.get('/otel_drop_in_baggage_api_datadog', async (request, reply) => { + const { setBaggageItem, removeBaggageItem } = require('dd-trace/packages/dd-trace/src/baggage') + + const url = request.query.url + console.log(url) + + const parsedUrl = new URL(url) + + const options = { + hostname: parsedUrl.hostname, + port: parsedUrl.port || 80, // Use default port if not provided + path: parsedUrl.pathname, + method: 'GET' + } + + const baggageRemove = request.query.baggage_remove + const baggageSet = request.query.baggage_set + const baggageToRemove = baggageRemove ? baggageRemove.split(',') : [] + const baggageToSet = baggageSet ? baggageSet.split(',').map(item => item.split('=')) : [] + + for (const key of baggageToRemove) { + console.log(`Removing baggage item: ${key.trim()}`) + removeBaggageItem(key.trim()) + } + for (const [key, value] of baggageToSet) { + console.log(`Setting baggage item: ${key.trim()} = ${value.trim()}`) + setBaggageItem(key.trim(), value.trim()) + } + + return new Promise((resolve, reject) => { + const httpRequest = http.request(options, (response) => { + let responseBody = '' + response.on('data', (chunk) => { + responseBody += chunk + }) + + response.on('end', () => { + resolve({ + url, + status_code: response.statusCode, + request_headers: response.req._headers, + response_headers: response.headers, + response_body: responseBody + }) + }) + }) + + httpRequest.on('error', (error) => { + console.log(error) + resolve({ + url, + status_code: 500, + request_headers: null, + response_headers: null + }) + }) + + httpRequest.end() + }) +}) + fastify.post('/shell_execution', async (request, reply) => { const { spawnSync } = require('child_process') const options = { shell: !!request?.body?.options?.shell } diff --git a/utils/build/docker/python/flask/app.py b/utils/build/docker/python/flask/app.py index 8e476ff49e1..ca226bbd597 100644 --- a/utils/build/docker/python/flask/app.py +++ b/utils/build/docker/python/flask/app.py @@ -8,6 +8,7 @@ monkey.patch_all(thread=True) # noqa: E402 +import contextlib import base64 import http.client import json @@ -1877,6 +1878,94 @@ def otel_drop_in_default_propagator_inject(): return jsonify(result) +# From https://github.com/open-telemetry/opentelemetry-python/issues/2432#issuecomment-1742425474 +# This context manager handles correctly managing context with repeated baggage operations +@contextlib.contextmanager +def otel_baggage( + baggage_to_remove=None, + baggage_to_set=None, + context=None, +): + attached_context_tokens: list[object] = list() + + if baggage_to_remove: + for key in baggage_to_remove: + attached_token = opentelemetry.baggage.remove_baggage(key, context) + attached_context_tokens.append(opentelemetry.context.attach(attached_token)) + + if baggage_to_set: + for key, value in baggage_to_set.items(): + attached_token = opentelemetry.baggage.set_baggage(key, value, context) + attached_context_tokens.append(opentelemetry.context.attach(attached_token)) + + try: + yield + finally: + for attached_token in attached_context_tokens: + opentelemetry.context.detach(attached_token) + + +@app.route("/otel_drop_in_baggage_api_otel", methods=["GET"]) +def otel_drop_in_baggage_api_otel(): + url = flask_request.args["url"] + baggage_remove_header = flask_request.args["baggage_remove"] + baggage_set_header = flask_request.args["baggage_set"] + + baggage_to_remove = [key.strip() for key in baggage_remove_header.split(",")] if baggage_remove_header else None + baggage_to_set = ( + {key.strip(): value.strip() for key, value in [item.split("=") for item in baggage_set_header.split(",")]} + if baggage_set_header + else None + ) + + with otel_baggage(baggage_to_remove, baggage_to_set): + response = requests.get(url) + + result = { + "url": url, + "status_code": response.status_code, + "request_headers": dict(response.request.headers), + "response_headers": dict(response.headers), + } + + return result + + +@app.route("/otel_drop_in_baggage_api_datadog", methods=["GET"]) +def otel_drop_in_baggage_api_datadog(): + url = flask_request.args["url"] + baggage_remove_header = flask_request.args["baggage_remove"] + baggage_set_header = flask_request.args["baggage_set"] + + baggage_to_remove = [key.strip() for key in baggage_remove_header.split(",")] if baggage_remove_header else None + baggage_to_set = ( + {key.strip(): value.strip() for key, value in [item.split("=") for item in baggage_set_header.split(",")]} + if baggage_set_header + else None + ) + + span = tracer.current_span() + if span: + if baggage_to_remove: + for key in baggage_to_remove: + span.context.remove_baggage_item(key) + + if baggage_to_set: + for key, value in baggage_to_set.items(): + span.context.set_baggage_item(key, value) + + response = requests.get(url) + + result = { + "url": url, + "status_code": response.status_code, + "request_headers": dict(response.request.headers), + "response_headers": dict(response.headers), + } + + return result + + @app.route("/inferred-proxy/span-creation", methods=["GET"]) def inferred_proxy_span_creation(): headers = flask_request.args.get("headers", {}) diff --git a/utils/build/docker/ruby/README.md b/utils/build/docker/ruby/README.md index b965725debc..580cbe6a115 100644 --- a/utils/build/docker/ruby/README.md +++ b/utils/build/docker/ruby/README.md @@ -22,6 +22,7 @@ Rails 7.2 * `/otel_drop_in_default_propagator_extract` with `opentelemetry-{sdk,api}` gem * `/otel_drop_in_default_propagator_inject` with `opentelemetry-{sdk,api}` gem +* `/otel_drop_in_baggage_api_otel` with `opentelemetry-{sdk,api}` gem * `/read_file` * `/log/library` * `/kafka/*` with `ruby-kafka` gem diff --git a/utils/build/docker/ruby/rails72/app/controllers/system_test_controller.rb b/utils/build/docker/ruby/rails72/app/controllers/system_test_controller.rb index 683c8b88995..54e9ddaab12 100644 --- a/utils/build/docker/ruby/rails72/app/controllers/system_test_controller.rb +++ b/utils/build/docker/ruby/rails72/app/controllers/system_test_controller.rb @@ -214,6 +214,70 @@ def otel_drop_in_default_propagator_inject render json: JSON.generate(headers), content_type: 'application/json' end + def otel_drop_in_baggage_api_otel + baggage_to_remove = request.params[:baggage_remove].split(',') || [] + baggage_to_set = request.params[:baggage_set].split(',').map { |item| item.split('=') } || [] + + baggage_to_remove.each do |key| + OpenTelemetry::Baggage.remove_value(key) + end + baggage_to_set.each do |key, value| + OpenTelemetry::Baggage.set_value(key, value) + end + + url = params[:url] + uri = URI(url) + request = nil + response = nil + + Net::HTTP.start(uri.host, uri.port) do |http| + request = Net::HTTP::Get.new(uri) + + response = http.request(request) + end + + result = { + "url": url, + "status_code": response.code.to_i, + "request_headers": request.each_header.to_h, + "response_headers": response.each_header.to_h, + } + + render json: result + end + + def otel_drop_in_baggage_api_datadog + baggage_to_remove = request.params[:baggage_remove].split(',') || [] + baggage_to_set = request.params[:baggage_set].split(',').map { |item| item.split('=') } || [] + + baggage_to_remove.each do |key| + Datadog::Tracing.baggage.delete(key) + end + baggage_to_set.each do |key, value| + Datadog::Tracing.baggage[key] = value + end + + url = params[:url] + uri = URI(url) + request = nil + response = nil + + Net::HTTP.start(uri.host, uri.port) do |http| + request = Net::HTTP::Get.new(uri) + + response = http.request(request) + end + + result = { + "url": url, + "status_code": response.code.to_i, + "request_headers": request.each_header.to_h, + "response_headers": response.each_header.to_h, + } + + render json: result + end + def handle_path_params render plain: 'OK' end diff --git a/utils/build/docker/ruby/rails72/config/routes.rb b/utils/build/docker/ruby/rails72/config/routes.rb index 4e20a61ae0d..101fc04e625 100644 --- a/utils/build/docker/ruby/rails72/config/routes.rb +++ b/utils/build/docker/ruby/rails72/config/routes.rb @@ -62,6 +62,8 @@ def call(_env) get '/otel_drop_in_default_propagator_extract' => 'system_test#otel_drop_in_default_propagator_extract' get '/otel_drop_in_default_propagator_inject' => 'system_test#otel_drop_in_default_propagator_inject' + get '/otel_drop_in_baggage_api_otel' => 'system_test#otel_drop_in_baggage_api_otel' + get '/otel_drop_in_baggage_api_datadog' => 'system_test#otel_drop_in_baggage_api_datadog' get '/debugger/init' => 'debugger#init' get '/debugger/pii' => 'debugger#pii'