Skip to content

Commit

Permalink
[Tracing] Add test and endpoints to test OTEL Drop-In Support of the …
Browse files Browse the repository at this point in the history
…OpenTelemetry Propagators API (#3782)
  • Loading branch information
zacharycmontoya authored Jan 30, 2025
1 parent b0f3126 commit 81292dd
Show file tree
Hide file tree
Showing 24 changed files with 486 additions and 3 deletions.
5 changes: 4 additions & 1 deletion manifests/cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ 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)
otel/:
test_context_propagation.py:
Test_Otel_Context_Propagation_Default_Propagator_Api: irrelevant (library does not implement OpenTelemetry)
parametric/:
test_128_bit_traceids.py:
Test_128_Bit_Traceids: v1.0.1.dev
Expand Down
3 changes: 3 additions & 0 deletions manifests/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ tests/:
Test_AWS_API_Gateway_Inferred_Span_Creation: missing_feature
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
otel/:
test_context_propagation.py:
Test_Otel_Context_Propagation_Default_Propagator_Api: v3.9.0
parametric/:
test_config_consistency.py:
Test_Config_Dogstatsd: missing_feature (does not support hostname)
Expand Down
5 changes: 5 additions & 0 deletions manifests/golang.yml
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,11 @@ tests/:
Test_AWS_API_Gateway_Inferred_Span_Creation: missing_feature
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
otel/:
test_context_propagation.py:
Test_Otel_Context_Propagation_Default_Propagator_Api:
'*': incomplete_test_app (endpoint not implemented)
net-http: v1.70.1
parametric/:
test_config_consistency.py:
Test_Config_Dogstatsd: v1.72.0-dev
Expand Down
5 changes: 5 additions & 0 deletions manifests/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,11 @@ tests/:
spring-boot: v1.39.0
test_sql.py:
Test_Sql: bug (APMAPI-729)
otel/:
test_context_propagation.py:
Test_Otel_Context_Propagation_Default_Propagator_Api:
'*': incomplete_test_app (endpoint not implemented)
spring-boot: v1.39.0
parametric/:
test_config_consistency.py:
Test_Config_Dogstatsd: missing_feature (default hostname is inconsistent)
Expand Down
6 changes: 6 additions & 0 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,12 @@ tests/:
express5: *ref_5_26_0
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
otel/:
test_context_propagation.py:
Test_Otel_Context_Propagation_Default_Propagator_Api:
'*': incomplete_test_app (endpoint not implemented)
express4: *ref_5_26_0
express5: *ref_5_26_0
parametric/:
test_128_bit_traceids.py:
Test_128_Bit_Traceids: *ref_3_0_0
Expand Down
3 changes: 3 additions & 0 deletions manifests/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ tests/:
Test_AWS_API_Gateway_Inferred_Span_Creation: missing_feature
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
otel/:
test_context_propagation.py:
Test_Otel_Context_Propagation_Default_Propagator_Api: incomplete_test_app (endpoint not implemented)
parametric/:
test_128_bit_traceids.py:
Test_128_Bit_Traceids: v0.84.0
Expand Down
5 changes: 5 additions & 0 deletions manifests/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,11 @@ tests/:
Test_AWS_API_Gateway_Inferred_Span_Creation: missing_feature
test_otel_drop_in.py:
Test_Otel_Drop_In: missing_feature
otel/:
test_context_propagation.py:
Test_Otel_Context_Propagation_Default_Propagator_Api:
'*': incomplete_test_app (endpoint not implemented)
flask-poc: v2.19.0
parametric/:
test_128_bit_traceids.py:
Test_128_Bit_Traceids: v2.6.0
Expand Down
5 changes: 5 additions & 0 deletions manifests/ruby.yml
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,11 @@ tests/:
TestK8sLibInjectioProfilingClusterEnabled: missing_feature
TestK8sLibInjectioProfilingClusterOverride: missing_feature
TestK8sLibInjectioProfilingDisabledByDefault: missing_feature
otel/:
test_context_propagation.py:
Test_Otel_Context_Propagation_Default_Propagator_Api:
'*': incomplete_test_app (endpoint not implemented)
rails70: v2.0.0
parametric/:
test_config_consistency.py:
Test_Config_Dogstatsd: missing_feature
Expand Down
41 changes: 41 additions & 0 deletions tests/otel/test_context_propagation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Unless explicitly stated otherwise all files in this repository are licensed under the the Apache License Version 2.0.
# This product includes software developed at Datadog (https://www.datadoghq.com/).
# Copyright 2024 Datadog, Inc.

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


@features.otel_propagators_api
@scenarios.apm_tracing_e2e_otel
class Test_Otel_Context_Propagation_Default_Propagator_Api:
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)

@incomplete_test_app(library="nodejs", reason="Node.js extract endpoint doesn't seem to be working.")
@incomplete_test_app(library="ruby", reason="Ruby extract seems to fail even though it should be supported")
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()
9 changes: 9 additions & 0 deletions utils/_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -2372,5 +2372,14 @@ def debugger_code_origins(test_object):
pytest.mark.features(feature_id=360)(test_object)
return test_object

@staticmethod
def otel_propagators_api(test_object):
"""OpenTelemetry Propagators API
https://feature-parity.us1.prod.dog/#/?feature=361
"""
pytest.mark.features(feature_id=361)(test_object)
return test_object


features = _Features()
49 changes: 49 additions & 0 deletions utils/build/docker/dotnet/weblog/Endpoints/OtelDropInEndpoint.cs
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));
});
}
}
}
10 changes: 10 additions & 0 deletions utils/build/docker/dotnet/weblog/OpenTelemetryInstrumentation.cs
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
Loading

0 comments on commit 81292dd

Please sign in to comment.