Skip to content

Commit 3cf92d3

Browse files
committed
Merge branch 'test/tracing-e2e'
Add end-to-end tracing verification tests
2 parents a624395 + 7e2d352 commit 3cf92d3

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package observability
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
8+
"go.opentelemetry.io/otel"
9+
"go.opentelemetry.io/otel/attribute"
10+
"go.opentelemetry.io/otel/codes"
11+
"go.opentelemetry.io/otel/propagation"
12+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
13+
"go.opentelemetry.io/otel/sdk/trace/tracetest"
14+
)
15+
16+
func TestEndToEndSpanHierarchy(t *testing.T) {
17+
t.Parallel()
18+
19+
exporter := tracetest.NewInMemoryExporter()
20+
provider := sdktrace.NewTracerProvider(
21+
sdktrace.WithSampler(sdktrace.AlwaysSample()),
22+
sdktrace.WithSyncer(exporter),
23+
)
24+
otel.SetTracerProvider(provider)
25+
defer func() { _ = provider.Shutdown(context.Background()) }()
26+
27+
tracer := otel.Tracer("test-tracer")
28+
ctx, rootSpan := tracer.Start(context.Background(), "controlplane.syncer.publish_snapshot")
29+
rootSpan.SetAttributes(
30+
attribute.String("snapshot.scope", "full"),
31+
attribute.Int("snapshot.gateway_key_count", 3),
32+
)
33+
34+
_, buildSpan := tracer.Start(ctx, "controlplane.syncer.build_snapshot")
35+
buildSpan.SetAttributes(attribute.Int("snapshot.route_key_count", 150))
36+
buildSpan.End()
37+
38+
_, infraSpan := tracer.Start(ctx, "controlplane.infrastructure.reconcile")
39+
infraSpan.SetAttributes(attribute.Int("infrastructure.managed_gateways", 2))
40+
infraSpan.End()
41+
42+
rootSpan.RecordError(errors.New("test error"))
43+
rootSpan.SetStatus(codes.Error, "test error")
44+
rootSpan.End()
45+
46+
provider.ForceFlush(context.Background())
47+
spans := exporter.GetSpans()
48+
49+
if len(spans) < 3 {
50+
t.Fatalf("expected >= 3 spans, got %d", len(spans))
51+
}
52+
53+
var hasRoot, hasBuild, hasInfra bool
54+
for _, s := range spans {
55+
switch s.Name {
56+
case "controlplane.syncer.publish_snapshot":
57+
hasRoot = true
58+
if s.Status.Code != codes.Error {
59+
t.Error("root span should have error status")
60+
}
61+
case "controlplane.syncer.build_snapshot":
62+
hasBuild = true
63+
if s.Parent.SpanID() != rootSpan.SpanContext().SpanID() {
64+
t.Error("build span should be child of root span")
65+
}
66+
case "controlplane.infrastructure.reconcile":
67+
hasInfra = true
68+
}
69+
}
70+
71+
if !hasRoot {
72+
t.Error("root span not found")
73+
}
74+
if !hasBuild {
75+
t.Error("build span not found")
76+
}
77+
if !hasInfra {
78+
t.Error("infra span not found")
79+
}
80+
}
81+
82+
func TestEndToEndAIInferenceSpan(t *testing.T) {
83+
t.Parallel()
84+
85+
exporter := tracetest.NewInMemoryExporter()
86+
provider := sdktrace.NewTracerProvider(
87+
sdktrace.WithSampler(sdktrace.AlwaysSample()),
88+
sdktrace.WithSyncer(exporter),
89+
)
90+
otel.SetTracerProvider(provider)
91+
defer func() { _ = provider.Shutdown(context.Background()) }()
92+
93+
tracer := otel.Tracer("test-ai-tracer")
94+
_, span := tracer.Start(context.Background(), "ai.inference")
95+
span.SetAttributes(
96+
attribute.String("ai.model", "gpt-4o"),
97+
attribute.String("ai.format", "openai"),
98+
attribute.Bool("ai.stream", false),
99+
attribute.Int64("ai.prompt_tokens", 150),
100+
attribute.Int64("ai.completion_tokens", 80),
101+
)
102+
span.End()
103+
104+
provider.ForceFlush(context.Background())
105+
spans := exporter.GetSpans()
106+
107+
if len(spans) != 1 {
108+
t.Fatalf("expected 1 span, got %d", len(spans))
109+
}
110+
if spans[0].Name != "ai.inference" {
111+
t.Errorf("span name = %q, want ai.inference", spans[0].Name)
112+
}
113+
114+
attrs := spans[0].Attributes
115+
if v := attrVal(attrs, "ai.model"); v != "gpt-4o" {
116+
t.Errorf("ai.model = %q, want gpt-4o", v)
117+
}
118+
if v := attrIntVal(attrs, "ai.prompt_tokens"); v != 150 {
119+
t.Errorf("ai.prompt_tokens = %d, want 150", v)
120+
}
121+
}
122+
123+
func TestEndToEndW3CPropagationEnabled(t *testing.T) {
124+
t.Parallel()
125+
126+
otel.SetTextMapPropagator(propagation.TraceContext{})
127+
prop := otel.GetTextMapPropagator()
128+
fields := prop.Fields()
129+
if len(fields) == 0 {
130+
t.Error("text map propagator should have fields for W3C traceparent")
131+
}
132+
}
133+
134+
func attrVal(attrs []attribute.KeyValue, key string) string {
135+
for _, a := range attrs {
136+
if string(a.Key) == key {
137+
return a.Value.AsString()
138+
}
139+
}
140+
return ""
141+
}
142+
143+
func attrIntVal(attrs []attribute.KeyValue, key string) int64 {
144+
for _, a := range attrs {
145+
if string(a.Key) == key {
146+
return a.Value.AsInt64()
147+
}
148+
}
149+
return -1
150+
}

0 commit comments

Comments
 (0)