From 01962ff77350ee3b4be867be1626d230f6a2c3ca Mon Sep 17 00:00:00 2001 From: jonny rhea Date: Thu, 8 Jan 2026 16:53:32 -0600 Subject: [PATCH] move OpenTelemetry SDK wiring out of cmd/utils --- cmd/geth/config.go | 10 +-- cmd/utils/rpc_tracing.go | 132 ---------------------------- cmd/utils/telemetry.go | 80 +++++++++++++++++ internal/telemetry/config/config.go | 95 ++++++++++++++++++++ 4 files changed, 180 insertions(+), 137 deletions(-) delete mode 100644 cmd/utils/rpc_tracing.go create mode 100644 cmd/utils/telemetry.go create mode 100644 internal/telemetry/config/config.go 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 +}