Skip to content
Merged
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: 1 addition & 1 deletion cmd/opencodereview/review_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func runReview(args []string) error {
q := newQuietHandle(opts.outputFormat, opts.audience)
defer q.Restore()

ctx, span := telemetry.StartSpan(context.Background(), "review.run")
ctx, span := telemetry.StartSpan(telemetry.ContextWithTraceParentFromEnv(context.Background()), "review.run")
defer span.End()
telemetry.SetAttr(span, "review.repo", cc.RepoDir)
telemetry.SetAttr(span, "review.from", opts.from)
Expand Down
2 changes: 1 addition & 1 deletion cmd/opencodereview/scan_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func runScan(args []string) error {
q := newQuietHandle(opts.outputFormat, opts.audience)
defer q.Restore()

ctx, span := telemetry.StartSpan(context.Background(), "scan.run")
ctx, span := telemetry.StartSpan(telemetry.ContextWithTraceParentFromEnv(context.Background()), "scan.run")
defer span.End()
var traceID string
if telemetry.IsEnabled() {
Expand Down
5 changes: 5 additions & 0 deletions internal/telemetry/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
Expand Down Expand Up @@ -57,6 +58,10 @@ func Init(ctx context.Context) bool {

otel.SetTracerProvider(tracerProvider)
otel.SetMeterProvider(meterProvider)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))

return len(shutdownFuncs) > 0
}
Expand Down
16 changes: 16 additions & 0 deletions internal/telemetry/span.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ package telemetry
import (
"context"
"fmt"
"os"
"time"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)

Expand All @@ -24,6 +26,20 @@ func StartSpan(ctx context.Context, name string, opts ...trace.SpanStartOption)
return getTracer().Start(ctx, name, opts...)
}

// ContextWithTraceParentFromEnv extracts W3C traceparent from the TRACEPARENT
// environment variable and returns a context carrying the upstream span context.
// Returns ctx unchanged when telemetry is disabled or the variable is unset.
func ContextWithTraceParentFromEnv(ctx context.Context) context.Context {
if !IsEnabled() {
return ctx
}
tp := os.Getenv("TRACEPARENT")
if tp == "" {
return ctx
}
return otel.GetTextMapPropagator().Extract(ctx, propagation.MapCarrier{"traceparent": tp})
}

// TraceIDFromContext returns the hex-encoded trace ID of the span carried by
// ctx, or "" if ctx carries no valid span (e.g. telemetry is disabled).
func TraceIDFromContext(ctx context.Context) string {
Expand Down
72 changes: 72 additions & 0 deletions internal/telemetry/span_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"testing"
"time"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)

Expand Down Expand Up @@ -170,3 +172,73 @@ func TestRecordLLMResult_NilSpan(t *testing.T) {
RecordLLMResult(nil, 100*time.Millisecond, 0, nil)
RecordLLMResult(nil, 100*time.Millisecond, 0, fmt.Errorf("err"))
}

// A well-formed W3C traceparent: version-traceID-spanID-flags.
const validTraceParent = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"
const wantExtractedTraceID = "0af7651916cd43dd8448eb211c80319c"

func TestContextWithTraceParentFromEnv_Extracts(t *testing.T) {
setupEnabledTelemetry(t)
// Init registers TraceContext+Baggage at the global propagator; mirror
// that here so Extract works without going through the full Init path.
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{}))

t.Setenv("TRACEPARENT", validTraceParent)
ctx := ContextWithTraceParentFromEnv(context.Background())

sc := trace.SpanContextFromContext(ctx)
if !sc.IsValid() {
t.Fatal("expected a valid SpanContext after extracting TRACEPARENT")
}
if got := sc.TraceID().String(); got != wantExtractedTraceID {
t.Errorf("extracted TraceID = %q, want %q", got, wantExtractedTraceID)
}

// A span started on this ctx must inherit the upstream trace (child, not root).
_, span := StartSpan(ctx, "test.child")
defer span.End()
if got := span.SpanContext().TraceID().String(); got != wantExtractedTraceID {
t.Errorf("child span TraceID = %q, want %q (must inherit upstream)", got, wantExtractedTraceID)
}
}

func TestContextWithTraceParentFromEnv_AbsentOrDisabled(t *testing.T) {
// Telemetry disabled: must short-circuit and leave ctx with no span context.
initialized = false
shutdownFuncs = nil
t.Setenv("TRACEPARENT", validTraceParent)
ctx := ContextWithTraceParentFromEnv(context.Background())
if sc := trace.SpanContextFromContext(ctx); sc.IsValid() {
t.Error("expected no valid SpanContext when telemetry is disabled")
}

// Telemetry enabled but TRACEPARENT unset: ctx unchanged (no upstream parent).
setupEnabledTelemetry(t)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{}))
t.Setenv("TRACEPARENT", "")
ctx = ContextWithTraceParentFromEnv(context.Background())
if sc := trace.SpanContextFromContext(ctx); sc.IsValid() {
t.Error("expected no valid SpanContext when TRACEPARENT is unset")
}
}

func TestContextWithTraceParentFromEnv_Malformed(t *testing.T) {
setupEnabledTelemetry(t)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{}, propagation.Baggage{}))

for _, bad := range []string{
"not-a-traceparent",
"00-invalidtraceid-invalidspanid-01",
} {
t.Run(bad, func(t *testing.T) {
t.Setenv("TRACEPARENT", bad)
ctx := ContextWithTraceParentFromEnv(context.Background())
if sc := trace.SpanContextFromContext(ctx); sc.IsValid() {
t.Errorf("expected no valid SpanContext for malformed TRACEPARENT %q", bad)
}
})
}
}
Loading