diff --git a/rpc/client.go b/rpc/client.go index 8d81503d59..89cc8235a2 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -29,6 +29,7 @@ import ( "time" "github.com/ethereum/go-ethereum/log" + "go.opentelemetry.io/otel/trace" ) var ( @@ -88,6 +89,7 @@ type Client struct { // config fields batchItemLimit int batchResponseMaxSize int + tracerProvider trace.TracerProvider // writeConn is used for writing to the connection on the caller's goroutine. It should // only be accessed outside of dispatch, with the write lock held. The write lock is @@ -119,7 +121,7 @@ func (c *Client) newClientConn(conn ServerCodec) *clientConn { ctx := context.Background() ctx = context.WithValue(ctx, clientContextKey{}, c) ctx = context.WithValue(ctx, peerInfoContextKey{}, conn.peerInfo()) - handler := newHandler(ctx, conn, c.idgen, c.services, c.batchItemLimit, c.batchResponseMaxSize, nil) + handler := newHandler(ctx, conn, c.idgen, c.services, c.batchItemLimit, c.batchResponseMaxSize, c.tracerProvider) return &clientConn{conn, handler} } @@ -247,6 +249,7 @@ func initClient(conn ServerCodec, services *serviceRegistry, cfg *clientConfig) idgen: cfg.idgen, batchItemLimit: cfg.batchItemLimit, batchResponseMaxSize: cfg.batchResponseLimit, + tracerProvider: cfg.tracerProvider, writeConn: conn, close: make(chan struct{}), closing: make(chan struct{}), diff --git a/rpc/client_opt.go b/rpc/client_opt.go index 3fa045a9b9..49cdb54c8a 100644 --- a/rpc/client_opt.go +++ b/rpc/client_opt.go @@ -20,6 +20,7 @@ import ( "net/http" "github.com/gorilla/websocket" + "go.opentelemetry.io/otel/trace" ) // ClientOption is a configuration option for the RPC client. @@ -41,6 +42,7 @@ type clientConfig struct { idgen func() ID batchItemLimit int batchResponseLimit int + tracerProvider trace.TracerProvider } func (cfg *clientConfig) initHeaders() { diff --git a/rpc/server.go b/rpc/server.go index 94d4a3e13e..6f6c44ebf5 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -126,6 +126,7 @@ func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) { idgen: s.idgen, batchItemLimit: s.batchItemLimit, batchResponseLimit: s.batchResponseLimit, + tracerProvider: s.tracerProvider, } c := initClient(codec, &s.services, cfg) <-codec.closed() diff --git a/rpc/tracing_test.go b/rpc/tracing_test.go index 5a04c901fd..b1af750045 100644 --- a/rpc/tracing_test.go +++ b/rpc/tracing_test.go @@ -19,6 +19,7 @@ package rpc import ( "context" "net/http/httptest" + "strings" "testing" "go.opentelemetry.io/otel" @@ -240,8 +241,8 @@ func TestTracingBatchHTTP(t *testing.T) { } // TestTracingSubscribeUnsubscribe verifies that subscribe and unsubscribe calls -// do not emit any spans. -// Note: This works because client.newClientConn() passes nil as the tracer provider. +// do not emit RPC server spans (like "jsonrpc.service/method"). +// Note: handleSubscribe does not create the main RPC span, only internal encoding spans. func TestTracingSubscribeUnsubscribe(t *testing.T) { t.Parallel() server, tracer, exporter := newTracingServer(t) @@ -257,12 +258,16 @@ func TestTracingSubscribeUnsubscribe(t *testing.T) { // Unsubscribe. sub.Unsubscribe() - // Flush and check that no spans were emitted. + // Flush and check that no RPC server spans were emitted. + // Internal spans like "rpc.encodeJSONResponse" are allowed. if err := tracer.ForceFlush(context.Background()); err != nil { t.Fatalf("failed to flush: %v", err) } spans := exporter.GetSpans() - if len(spans) != 0 { - t.Errorf("expected no spans for subscribe/unsubscribe, got %d", len(spans)) + for _, span := range spans { + // RPC server spans have names like "jsonrpc.service/method" + if strings.HasPrefix(span.Name, "jsonrpc.") { + t.Errorf("unexpected RPC server span for subscribe/unsubscribe: %s", span.Name) + } } }