Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tracing] Add test and endpoints to test OTEL Drop-In Support of the OpenTelemetry Propagators API #3782

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
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
3 changes: 2 additions & 1 deletion manifests/cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ tests/:
test_inferred_proxy.py:
Test_AWS_API_Gateway_Inferred_Span_Creation: missing_feature
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
Test_Otel_Drop_In: irrelevant (library does not implement OpenTelemetry)
Test_Otel_Drop_In_Manual_Propagation: irrelevant (library does not implement OpenTelemetry)
parametric/:
test_128_bit_traceids.py:
Test_128_Bit_Traceids: missing_feature (parametric app does not support trace/span/extract endpoint)
Expand Down
1 change: 1 addition & 0 deletions manifests/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ tests/:
Test_AWS_API_Gateway_Inferred_Span_Creation: missing_feature
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
Test_Otel_Drop_In_Manual_Propagation: missing_feature
parametric/:
test_config_consistency.py:
Test_Config_Dogstatsd: missing_feature (does not support hostname)
Expand Down
3 changes: 3 additions & 0 deletions manifests/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,9 @@ tests/:
Test_AWS_API_Gateway_Inferred_Span_Creation: missing_feature
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
Test_Otel_Drop_In_Default_Propagator:
'*': irrelevant
net-http: v1.70.1
parametric/:
test_config_consistency.py:
Test_Config_Dogstatsd: missing_feature
Expand Down
3 changes: 3 additions & 0 deletions manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,9 @@ tests/:
Test_Otel_Drop_In:
'*': missing_feature
spring-boot: v1.39.0
Test_Otel_Drop_In_Default_Propagator:
'*': missing_feature
spring-boot: v1.39.0
test_sql.py:
Test_Sql: bug (APMAPI-729)
parametric/:
Expand Down
4 changes: 4 additions & 0 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,10 @@ tests/:
express5: *ref_5_26_0
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
Test_Otel_Drop_In_Default_Propagator:
'*': irrelevant
express4: *ref_5_26_0
express5: *ref_5_26_0
parametric/:
test_config_consistency.py:
Test_Config_Dogstatsd: *ref_5_29_0
Expand Down
3 changes: 3 additions & 0 deletions manifests/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,9 @@ tests/:
Test_AWS_API_Gateway_Inferred_Span_Creation: missing_feature
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
Test_Otel_Drop_In_Default_Propagator:
'*': irrelevant
flask-poc: v2.19.0
parametric/:
test_128_bit_traceids.py:
Test_128_Bit_Traceids: v2.6.0
Expand Down
36 changes: 35 additions & 1 deletion tests/integrations/test_otel_drop_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# This product includes software developed at Datadog (https://www.datadoghq.com/).
# Copyright 2024 Datadog, Inc.

from utils import weblog, interfaces, scenarios, features
import json
from utils import weblog, interfaces, scenarios, features, incomplete_test_app


@features.f_otel_interoperability
Expand Down Expand Up @@ -39,3 +40,36 @@ def has_otel_library_tag(tags):
span_metric_found = True
break
assert span_metric_found, "Otel drop-in span metric not found"


@features.f_otel_interoperability
@scenarios.apm_tracing_e2e_otel
class Test_Otel_Drop_In_Default_Propagator:
def setup_propagation_extract(self):
extract_headers = {
"traceparent": "00-11111111111111110000000000000002-000000000000000a-01",
"tracestate": "dd=s:2;p:000000000000000a,foo=1",
"baggage": "foo=1",
}
self.r = weblog.get("/otel_drop_in_default_propagator_extract", headers=extract_headers)

def test_propagation_extract(self):
content = json.loads(self.r.text)

assert content["trace_id"] == 2
assert content["span_id"] == 10
assert content["tracestate"] and not content["tracestate"].isspace()
# assert content["baggage"] and not content["baggage"].isspace()

def setup_propagation_inject(self):
inject_headers = {
"baggage": "foo=2",
}
self.r = weblog.get("/otel_drop_in_default_propagator_inject")

@incomplete_test_app(library="nodejs", reason="Node.js inject endpoint doesn't seem to be working.")
def test_propagation_inject(self):
content = json.loads(self.r.text)

assert content["traceparent"] and not content["traceparent"].isspace()
# assert content["baggage"] and not content["baggage"].isspace()
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using OpenTelemetry;
using OpenTelemetry.Context.Propagation;

namespace weblog
{
public class OtelDropInEndpoint : ISystemTestEndpoint
{
public void Register(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder routeBuilder)
{
routeBuilder.MapGet("/otel_drop_in_default_propagator_extract", async context =>
{
var parentContext = OpenTelemetryInstrumentation.Propagator.Extract(default, context.Request.Headers, (carrier, key) =>
{
return carrier.TryGetValue(key, out var value) && value.Count >= 1 ? new[] { value[0] } : null;
});

var ddTraceId = Convert.ToUInt64(parentContext.ActivityContext.TraceId.ToHexString().Substring(16), 16);
var ddSpanId = Convert.ToUInt64(parentContext.ActivityContext.SpanId.ToHexString(), 16);

var data = new
{
trace_id = ddTraceId,
span_id = ddSpanId,
tracestate = parentContext.ActivityContext.TraceState,
baggage = parentContext.Baggage
};

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

routeBuilder.MapGet("/otel_drop_in_default_propagator_inject", async context =>
{
var headersDict = new Dictionary<string,string>();
OpenTelemetryInstrumentation.Propagator.Inject(new PropagationContext(Activity.Current.Context, Baggage.Current), headersDict, (carrier, key, value) =>
{
carrier[key] = value;
});

await context.Response.WriteAsync(JsonSerializer.Serialize(headersDict));
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Diagnostics;
using OpenTelemetry.Context.Propagation;

namespace weblog
{
public static class OpenTelemetryInstrumentation
{
public static TextMapPropagator Propagator { get; } = Propagators.DefaultTextMapPropagator;
}
}
1 change: 1 addition & 0 deletions utils/build/docker/dotnet/weblog/app.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@
<PackageReference Include="MongoDB.Driver" Version="2.23.1"/>

<PackageReference Include="Datadog.Trace" Version="*" />
<PackageReference Include="OpenTelemetry.Api" Version="1.10.0" />
</ItemGroup>
</Project>
112 changes: 112 additions & 0 deletions utils/build/docker/golang/app/net-http/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
oteltrace "go.opentelemetry.io/otel/trace"
otelbaggage "go.opentelemetry.io/otel/baggage"

"gopkg.in/DataDog/dd-trace-go.v1/appsec"
httptrace "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
Expand Down Expand Up @@ -537,6 +538,79 @@ func main() {
w.Write([]byte("ok"))
})

mux.HandleFunc("/otel_drop_in_default_propagator_extract", func(w http.ResponseWriter, r *http.Request) {
// Differing from other languages, the user must set the text map propagator because dd-trace-go
// doesn't automatically instrument at runtime (not including Orchestrion)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))

httpCarrier := HttpCarrier{header: r.Header}

propagator := otel.GetTextMapPropagator()
ctx := propagator.Extract(r.Context(), httpCarrier)

spanContext := oteltrace.SpanContextFromContext(ctx)
baggage := otelbaggage.FromContext(ctx)

base := 16
bitSize := 64
result := make(map[string]any, 4)

num, err := strconv.ParseInt(spanContext.TraceID().String()[16:], base, bitSize)
if err == nil {
result["trace_id"] = num
}

num, err = strconv.ParseInt(spanContext.SpanID().String(), base, bitSize)
if err == nil {
result["span_id"] = num
}

result["tracestate"] = spanContext.TraceState().String()
result["baggage"] = baggage.String()

jsonData, err := json.Marshal(result)
if err != nil {
w.WriteHeader(422)
w.Write([]byte("failed to convert carrier to JSON"))
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(jsonData)
})

mux.HandleFunc("/otel_drop_in_default_propagator_inject", func(w http.ResponseWriter, r *http.Request) {
// Differing from other languages, the user must set the text map propagator because dd-trace-go
// doesn't automatically instrument at runtime (not including Orchestrion)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))

ctx := context.Background()
p := ddotel.NewTracerProvider()
tracer := p.Tracer("")
otel.SetTracerProvider(p)

_, span := tracer.Start(ddotel.ContextWithStartOptions(ctx), "main")
newCtx := oteltrace.ContextWithSpan(ctx, span)

propagator := otel.GetTextMapPropagator()
mapCarrier := make(MapCarrier)
propagator.Inject(newCtx, mapCarrier)

jsonData, err := json.Marshal(mapCarrier)
span.End()

if err != nil {
w.WriteHeader(422)
w.Write([]byte("failed to convert carrier to JSON"))
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
w.Write(jsonData)
})

mux.HandleFunc("/session/new", func(w http.ResponseWriter, r *http.Request) {
sessionID := strconv.Itoa(rand.Int())
w.Header().Add("Set-Cookie", "session="+sessionID+"; Path=/; Max-Age=3600; Secure; HttpOnly")
Expand Down Expand Up @@ -598,6 +672,44 @@ func (c carrier) ForeachKey(handler func(key, val string) error) error {
return nil
}

type MapCarrier map[string]string

func (c MapCarrier) Get(key string) string {
return c[key]
}

func (c MapCarrier) Set(key, val string) {
c[key] = val
}

func (c MapCarrier) Keys() []string {
keys := make([]string, 0, len(c))
for k := range c {
keys = append(keys, k)
}
return keys
}

type HttpCarrier struct {
header http.Header
}

func (c HttpCarrier) Get(key string) string {
return c.header.Get(key)
}

func (c HttpCarrier) Set(key, val string) {
c.header.Set(key, val)
}

func (c HttpCarrier) Keys() []string {
keys := make([]string, 0, len(c.header))
for k := range c.header {
keys = append(keys, k)
}
return keys
}

func write(w http.ResponseWriter, r *http.Request, d []byte) {
span, _ := ddtracer.StartSpanFromContext(r.Context(), "child.span")
defer span.Finish()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,19 @@
import org.springframework.web.server.ResponseStatusException;


import io.opentelemetry.api.baggage.Baggage;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.Context;
import io.opentracing.Span;
import io.opentracing.util.GlobalTracer;
import ognl.Ognl;
Expand Down Expand Up @@ -1094,6 +1100,60 @@ public String otelDropInSpan() {
return "OK";
}

@RequestMapping("/otel_drop_in_default_propagator_extract")
public String otelDropInDefaultPropagatorExtract(@RequestHeader Map<String, String> headers) throws com.fasterxml.jackson.core.JsonProcessingException {
ContextPropagators propagators = GlobalOpenTelemetry.getPropagators();
TextMapPropagator textMapPropagator = propagators.getTextMapPropagator();

Context extractedContext = textMapPropagator.extract(Context.current(), headers, new TextMapGetter<Map<String, String>>() {
@Override
public Iterable<String> keys(Map<String, String> map) {
return map.keySet();
}

@Override
public String get(Map<String, String> map, String key) {
return map.get(key);
}
});

io.opentelemetry.api.trace.SpanContext spanContext = io.opentelemetry.api.trace.Span.fromContext(extractedContext).getSpanContext();
Long ddTraceId = Long.parseLong(spanContext.getTraceId().substring(16), 16);
Long ddSpanId = Long.parseLong(spanContext.getSpanId(), 16);

Map<String, Object> map = new HashMap<>();
map.put("trace_id", ddTraceId);
map.put("span_id", ddSpanId);
map.put("tracestate", spanContext.getTraceState().asMap().toString());
map.put("baggage", Baggage.fromContext(extractedContext).asMap().toString());

// Convert headers map to JSON string
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(map);

return jsonString;
}

@RequestMapping("/otel_drop_in_default_propagator_inject")
public String otelDropInDefaultPropagatorInject() throws com.fasterxml.jackson.core.JsonProcessingException {
ContextPropagators propagators = GlobalOpenTelemetry.getPropagators();
TextMapPropagator textMapPropagator = propagators.getTextMapPropagator();

Map<String, String> map = new HashMap<>();
textMapPropagator.inject(Context.current(), map, new TextMapSetter<Map<String, String>>() {
@Override
public void set(Map<String, String> map, String key, String value) {
map.put(key, value);
}
});

// Convert headers map to JSON string
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(map);

return jsonString;
}

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