mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-26 07:37:20 +00:00
rpc: extract OpenTelemetry trace context from request headers (#33599)
This PR adds support for the extraction of OpenTelemetry trace context from incoming JSON-RPC request headers, allowing geth spans to be linked to upstream traces when present. --------- Co-authored-by: lightclient <lightclient@protonmail.com>
This commit is contained in:
parent
a9acb3ff93
commit
e3e556b266
2 changed files with 40 additions and 2 deletions
|
|
@ -30,6 +30,9 @@ import (
|
|||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/propagation"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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"])
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue