diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index d2430e17b0..5cd17e2f4c 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -242,13 +242,13 @@ func makeFullNode(ctx *cli.Context) *node.Node {
// Start metrics export if enabled
utils.SetupMetrics(&cfg.Metrics)
- // Setup RPC tracing if enabled
- rpcTracingService, err := utils.SetupRPCTracing(ctx)
+ // Setup telemetry if enabled
+ telemetryService, err := utils.SetupTelemetry(ctx)
if err != nil {
- utils.Fatalf("failed to setup rpc tracing: %v", err)
+ utils.Fatalf("failed to setup telemetry: %v", err)
}
- if rpcTracingService != nil {
- utils.RegisterRPCTracingService(rpcTracingService, stack)
+ if telemetryService != nil {
+ utils.RegisterTelemetryService(telemetryService, stack)
}
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
diff --git a/cmd/utils/rpc_tracing.go b/cmd/utils/rpc_tracing.go
deleted file mode 100644
index a85ba5fd71..0000000000
--- a/cmd/utils/rpc_tracing.go
+++ /dev/null
@@ -1,132 +0,0 @@
-// Copyright 2025 The go-ethereum Authors
-// This file is part of go-ethereum.
-//
-// go-ethereum is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// go-ethereum is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with go-ethereum. If not, see .
-
-package utils
-
-import (
- "context"
- "fmt"
- "net/url"
- "time"
-
- "github.com/ethereum/go-ethereum/internal/version"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/node"
- "github.com/urfave/cli/v2"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/attribute"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
- "go.opentelemetry.io/otel/propagation"
- "go.opentelemetry.io/otel/sdk/resource"
- sdktrace "go.opentelemetry.io/otel/sdk/trace"
- semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
-)
-
-// rpcTracingService wraps the OpenTelemetry TracerProvider to implement node.Lifecycle.
-type rpcTracingService struct {
- tracerProvider *sdktrace.TracerProvider
-}
-
-// Start implements node.Lifecycle.
-func (r *rpcTracingService) Start() error {
- return nil // Provider is already started during setup
-}
-
-// Stop implements node.Lifecycle
-func (r *rpcTracingService) Stop() error {
- ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
- defer cancel()
- if err := r.tracerProvider.Shutdown(ctx); err != nil {
- log.Error("Failed to stop RPC Tracing Service", "err", err)
- return err
- }
- log.Info("RPC Tracing Service stopped")
- return nil
-}
-
-// SetupRPCTracing initializes OpenTelemetry tracing based on CLI flags.
-func SetupRPCTracing(ctx *cli.Context) (*rpcTracingService, error) {
- if !ctx.Bool(RPCTracingFlag.Name) {
- return nil, nil
- }
- endpoint := ctx.String(RPCTracingEndpointFlag.Name)
- if endpoint == "" {
- return nil, nil
- }
- setupCtx := ctx.Context
- if setupCtx == nil {
- setupCtx = context.Background()
- }
-
- // Create exporter based on endpoint URL
- u, err := url.Parse(endpoint)
- if err != nil {
- return nil, fmt.Errorf("invalid rpc tracing endpoint URL: %w", err)
- }
-
- var exporter sdktrace.SpanExporter
- switch u.Scheme {
- case "http", "https":
- opts := []otlptracehttp.Option{
- otlptracehttp.WithEndpoint(u.Host),
- }
- if u.Scheme == "http" {
- opts = append(opts, otlptracehttp.WithInsecure())
- }
- if u.Path != "" && u.Path != "/" {
- opts = append(opts, otlptracehttp.WithURLPath(u.Path))
- }
- exporter, err = otlptracehttp.New(setupCtx, opts...)
- default:
- return nil, fmt.Errorf("unsupported rpc tracing url scheme: %s", u.Scheme)
- }
-
- if err != nil {
- return nil, fmt.Errorf("failed to create rpc tracing exporter: %w", err)
- }
-
- // Create resource with service information
- res := resource.NewWithAttributes(
- semconv.SchemaURL,
- semconv.ServiceName("geth"),
- attribute.String("client.name", version.ClientName("geth")),
- )
-
- // Create TracerProvider
- tp := sdktrace.NewTracerProvider(
- sdktrace.WithBatcher(exporter),
- sdktrace.WithResource(res),
- )
-
- // Set as global provider
- otel.SetTracerProvider(tp)
-
- // Set global propagator for context propagation
- // Note: This should enable distributed tracing
- otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
- propagation.TraceContext{},
- propagation.Baggage{},
- ))
-
- log.Info("RPC tracing enabled", "endpoint", endpoint)
- return &rpcTracingService{tracerProvider: tp}, nil
-}
-
-// RegisterRPCTracingService registers the rpcTracingService with the node
-// so its lifecycle is managed by the node.
-func RegisterRPCTracingService(rpcTracingService *rpcTracingService, stack *node.Node) {
- stack.RegisterLifecycle(rpcTracingService)
-}
diff --git a/cmd/utils/telemetry.go b/cmd/utils/telemetry.go
new file mode 100644
index 0000000000..e54168a2ab
--- /dev/null
+++ b/cmd/utils/telemetry.go
@@ -0,0 +1,80 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of go-ethereum.
+//
+// go-ethereum is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// go-ethereum is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with go-ethereum. If not, see .
+
+package utils
+
+import (
+ "context"
+ "time"
+
+ "github.com/ethereum/go-ethereum/internal/telemetry/config"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/urfave/cli/v2"
+)
+
+// telemetryService wraps the telemetry Provider to implement node.Lifecycle.
+type telemetryService struct {
+ provider *config.Provider
+}
+
+// Start implements node.Lifecycle.
+func (t *telemetryService) Start() error {
+ return nil // Provider is already started during setup
+}
+
+// Stop implements node.Lifecycle.
+func (t *telemetryService) Stop() error {
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ if err := t.provider.Shutdown(ctx); err != nil {
+ log.Error("Failed to stop telemetry service", "err", err)
+ return err
+ }
+ log.Info("Telemetry stopped")
+ return nil
+}
+
+// SetupTelemetry initializes OpenTelemetry tracing based on CLI flags.
+func SetupTelemetry(ctx *cli.Context) (*telemetryService, error) {
+ if !ctx.Bool(RPCTracingFlag.Name) {
+ return nil, nil
+ }
+ endpoint := ctx.String(RPCTracingEndpointFlag.Name)
+ if endpoint == "" {
+ return nil, nil
+ }
+ setupCtx := ctx.Context
+ if setupCtx == nil {
+ setupCtx = context.Background()
+ }
+
+ // Configure OpenTelemetry tracing
+ handle, err := config.Setup(
+ setupCtx,
+ endpoint,
+ )
+ if err != nil {
+ return nil, err
+ }
+ log.Info("Telemetry enabled", "endpoint", endpoint)
+ return &telemetryService{provider: handle}, nil
+}
+
+// RegisterTelemetryService registers the telemetryService with the node.
+func RegisterTelemetryService(service *telemetryService, stack *node.Node) {
+ stack.RegisterLifecycle(service)
+}
diff --git a/internal/telemetry/config/config.go b/internal/telemetry/config/config.go
new file mode 100644
index 0000000000..c0d56231bf
--- /dev/null
+++ b/internal/telemetry/config/config.go
@@ -0,0 +1,95 @@
+// Copyright 2025 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package config
+
+import (
+ "context"
+ "fmt"
+ "net/url"
+
+ "github.com/ethereum/go-ethereum/internal/version"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
+ "go.opentelemetry.io/otel/propagation"
+ "go.opentelemetry.io/otel/sdk/resource"
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
+ semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
+)
+
+// Provider wraps a TracerProvider and exposes a Shutdown method.
+type Provider struct {
+ tracerProvider *sdktrace.TracerProvider
+}
+
+// Shutdown shuts down the tracer provider.
+func (p *Provider) Shutdown(ctx context.Context) error {
+ return p.tracerProvider.Shutdown(ctx)
+}
+
+// Setup initializes OpenTelemetry tracing with the given parameters.
+func Setup(ctx context.Context, endpoint string) (*Provider, error) {
+ // Create exporter based on endpoint URL
+ u, err := url.Parse(endpoint)
+ if err != nil {
+ return nil, fmt.Errorf("invalid rpc tracing endpoint URL: %w", err)
+ }
+ var exporter sdktrace.SpanExporter
+ switch u.Scheme {
+ case "http", "https":
+ opts := []otlptracehttp.Option{
+ otlptracehttp.WithEndpoint(u.Host),
+ }
+ if u.Scheme == "http" {
+ opts = append(opts, otlptracehttp.WithInsecure())
+ }
+ if u.Path != "" && u.Path != "/" {
+ opts = append(opts, otlptracehttp.WithURLPath(u.Path))
+ }
+ exporter, err = otlptracehttp.New(ctx, opts...)
+ default:
+ return nil, fmt.Errorf("unsupported telemetry url scheme: %s", u.Scheme)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("failed to create telemetry exporter: %w", err)
+ }
+
+ // Create resource with service and client information
+ res := resource.NewWithAttributes(
+ semconv.SchemaURL,
+ semconv.ServiceName("geth"),
+ attribute.String("client.name", version.ClientName("geth")),
+ )
+
+ // Create TracerProvider
+ tp := sdktrace.NewTracerProvider(
+ sdktrace.WithBatcher(exporter),
+ sdktrace.WithResource(res),
+ )
+
+ // Set as global provider
+ otel.SetTracerProvider(tp)
+
+ // Set global propagator for context propagation
+ // Note: This is needed for distributed tracing
+ otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
+ propagation.TraceContext{},
+ propagation.Baggage{},
+ ))
+
+ return &Provider{tracerProvider: tp}, nil
+}