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
6 changes: 6 additions & 0 deletions rpc/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ import (
"strconv"
"sync"
"time"

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

const (
Expand Down Expand Up @@ -334,6 +337,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, peerInfoContextKey{}, connInfo)

// Extract trace context from incoming headers.
ctx = otel.GetTextMapPropagator().Extract(ctx, propagation.HeaderCarrier(r.Header))

// All checks passed, create a codec that reads directly from the request body
// until EOF, writes the response to w, and orders the server to process a
// single request.
Expand Down
36 changes: 34 additions & 2 deletions rpc/tracing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ import (
"net/http/httptest"
"testing"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/propagation"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
)
Expand Down Expand Up @@ -60,16 +62,33 @@ func newTracingServer(t *testing.T) (*Server, *sdktrace.TracerProvider, *tracete

// TestTracingHTTP verifies that RPC spans are emitted when processing HTTP requests.
func TestTracingHTTP(t *testing.T) {
t.Parallel()
// Not parallel: this test modifies the global otel TextMapPropagator.

// Set up a propagator to extract W3C Trace Context headers.
originalPropagator := otel.GetTextMapPropagator()
otel.SetTextMapPropagator(propagation.TraceContext{})
t.Cleanup(func() { otel.SetTextMapPropagator(originalPropagator) })

server, tracer, exporter := newTracingServer(t)
httpsrv := httptest.NewServer(server)
t.Cleanup(httpsrv.Close)

// Define the expected trace and span IDs for context propagation.
const (
traceID = "4bf92f3577b34da6a3ce929d0e0e4736"
parentSpanID = "00f067aa0ba902b7"
traceparent = "00-" + traceID + "-" + parentSpanID + "-01"
)

client, err := DialHTTP(httpsrv.URL)
if err != nil {
t.Fatalf("failed to dial: %v", err)
}
t.Cleanup(client.Close)

// Set trace context headers.
client.SetHeader("traceparent", traceparent)

// Make a successful RPC call.
var result echoResult
if err := client.Call(&result, "test_echo", "hello", 42, &echoArgs{S: "world"}); err != nil {
Expand All @@ -92,8 +111,10 @@ func TestTracingHTTP(t *testing.T) {
}
}
if rpcSpan == nil {
t.Fatalf("jsonrpc.test/echo span not found.")
t.Fatalf("jsonrpc.test/echo span not found")
}

// Verify span attributes.
attrs := attributeMap(rpcSpan.Attributes)
if attrs["rpc.system"] != "jsonrpc" {
t.Errorf("expected rpc.system=jsonrpc, got %v", attrs["rpc.system"])
Expand All @@ -107,6 +128,17 @@ func TestTracingHTTP(t *testing.T) {
if _, ok := attrs["rpc.jsonrpc.request_id"]; !ok {
t.Errorf("expected rpc.jsonrpc.request_id attribute to be set")
}

// Verify the span's parent matches the traceparent header values.
if got := rpcSpan.Parent.TraceID().String(); got != traceID {
t.Errorf("parent trace ID mismatch: got %s, want %s", got, traceID)
}
if got := rpcSpan.Parent.SpanID().String(); got != parentSpanID {
t.Errorf("parent span ID mismatch: got %s, want %s", got, parentSpanID)
}
if !rpcSpan.Parent.IsRemote() {
t.Error("expected parent span context to be marked as remote")
}
}

// TestTracingBatchHTTP verifies that RPC spans are emitted for batched JSON-RPC calls over HTTP.
Expand Down