all: rework tracing setup

Changes are made to allows configuring tracing via TOML config file.
The tracing config is now a part of the node.Config.
This commit is contained in:
Felix Lange 2026-02-02 17:54:39 +01:00
parent fa4e707927
commit 4b7a648a82
6 changed files with 129 additions and 116 deletions

View file

@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/eth/catalyst" "github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/internal/flags" "github.com/ethereum/go-ethereum/internal/flags"
"github.com/ethereum/go-ethereum/internal/telemetry/tracesetup"
"github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -239,18 +240,15 @@ func makeFullNode(ctx *cli.Context) *node.Node {
cfg.Eth.OverrideVerkle = &v cfg.Eth.OverrideVerkle = &v
} }
// Start metrics export if enabled // Start metrics export if enabled.
utils.SetupMetrics(&cfg.Metrics) utils.SetupMetrics(&cfg.Metrics)
// Setup telemetry if enabled // Setup OpenTelemetry reporting if enabled.
telemetryService, err := utils.SetupTelemetry(ctx) if err := tracesetup.StartTelemetry(ctx.Context, cfg.Node.OpenTelemetry, stack); err != nil {
if err != nil { utils.Fatalf("failed to setup OpenTelemetry: %v", err)
utils.Fatalf("failed to setup telemetry: %v", err)
}
if telemetryService != nil {
utils.RegisterTelemetryService(telemetryService, stack)
} }
// Add Ethereum service.
backend, eth := utils.RegisterEthService(stack, &cfg.Eth) backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// Create gauge with geth system and build information // Create gauge with geth system and build information

View file

@ -198,6 +198,9 @@ var (
utils.RPCGlobalRangeLimitFlag, utils.RPCGlobalRangeLimitFlag,
utils.RPCTelemetryFlag, utils.RPCTelemetryFlag,
utils.RPCTelemetryEndpointFlag, utils.RPCTelemetryEndpointFlag,
utils.RPCTelemetryUserFlag,
utils.RPCTelemetryPasswordFlag,
utils.RPCTelemetryInstanceIDFlag,
utils.RPCTelemetrySampleRatioFlag, utils.RPCTelemetrySampleRatioFlag,
} }

View file

@ -1056,6 +1056,24 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Category: flags.APICategory, Category: flags.APICategory,
} }
RPCTelemetryUserFlag = &cli.StringFlag{
Name: "rpc.telemetry.username",
Usage: "HTTP Basic Auth username for OpenTelemetry",
Category: flags.APICategory,
}
RPCTelemetryPasswordFlag = &cli.StringFlag{
Name: "rpc.telemetry.password",
Usage: "HTTP Basic Auth password for OpenTelemetry",
Category: flags.APICategory,
}
RPCTelemetryInstanceIDFlag = &cli.StringFlag{
Name: "rpc.telemetry.instance-id",
Usage: "OpenTelemetry instance ID",
Category: flags.APICategory,
}
RPCTelemetrySampleRatioFlag = &cli.Float64Flag{ RPCTelemetrySampleRatioFlag = &cli.Float64Flag{
Name: "rpc.telemetry.sample-ratio", Name: "rpc.telemetry.sample-ratio",
Usage: "Defines the sampling ratio for RPC telemetry (0.0 to 1.0)", Usage: "Defines the sampling ratio for RPC telemetry (0.0 to 1.0)",
@ -1452,6 +1470,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
setNodeUserIdent(ctx, cfg) setNodeUserIdent(ctx, cfg)
SetDataDir(ctx, cfg) SetDataDir(ctx, cfg)
setSmartCard(ctx, cfg) setSmartCard(ctx, cfg)
setOpenTelemetry(ctx, cfg)
if ctx.IsSet(JWTSecretFlag.Name) { if ctx.IsSet(JWTSecretFlag.Name) {
cfg.JWTSecret = ctx.String(JWTSecretFlag.Name) cfg.JWTSecret = ctx.String(JWTSecretFlag.Name)
@ -1519,6 +1538,33 @@ func setSmartCard(ctx *cli.Context, cfg *node.Config) {
cfg.SmartCardDaemonPath = path cfg.SmartCardDaemonPath = path
} }
func setOpenTelemetry(ctx *cli.Context, cfg *node.Config) {
tcfg := &cfg.OpenTelemetry
if ctx.IsSet(RPCTelemetryFlag.Name) {
tcfg.Enabled = ctx.Bool(RPCTelemetryFlag.Name)
}
if ctx.IsSet(RPCTelemetryEndpointFlag.Name) {
tcfg.Endpoint = ctx.String(RPCTelemetryEndpointFlag.Name)
}
if ctx.IsSet(RPCTelemetryUserFlag.Name) {
tcfg.AuthUser = ctx.String(RPCTelemetryUserFlag.Name)
}
if ctx.IsSet(RPCTelemetryPasswordFlag.Name) {
tcfg.AuthPassword = ctx.String(RPCTelemetryPasswordFlag.Name)
}
if ctx.IsSet(RPCTelemetrySampleRatioFlag.Name) {
tcfg.SampleRatio = ctx.Float64(RPCTelemetrySampleRatioFlag.Name)
}
if ctx.IsSet(RPCTelemetryInstanceIDFlag.Name) {
tcfg.InstanceID = ctx.String(RPCTelemetryInstanceIDFlag.Name)
}
if tcfg.Endpoint != "" && !tcfg.Enabled {
log.Warn(fmt.Sprintf("OpenTelemetry endpoint configured but telemetry is not enabled, use --%s to enable.", RPCTelemetryFlag.Name))
}
}
func SetDataDir(ctx *cli.Context, cfg *node.Config) { func SetDataDir(ctx *cli.Context, cfg *node.Config) {
switch { switch {
case ctx.IsSet(DataDirFlag.Name): case ctx.IsSet(DataDirFlag.Name):

View file

@ -1,86 +0,0 @@
// Copyright 2026 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 <http://www.gnu.org/licenses/>.
package utils
import (
"context"
"fmt"
"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 TelemetryProvider to implement node.Lifecycle.
type telemetryService struct {
telemetryProvider *config.TelemetryProvider
}
// Start implements node.Lifecycle.
func (t *telemetryService) Start() error {
return nil // TelemetryProvider 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.telemetryProvider.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(RPCTelemetryFlag.Name) {
return nil, nil
}
endpoint := ctx.String(RPCTelemetryEndpointFlag.Name)
if endpoint == "" {
return nil, nil
}
sampleRatio := ctx.Float64(RPCTelemetrySampleRatioFlag.Name)
if sampleRatio < 0 || sampleRatio > 1 {
return nil, fmt.Errorf("invalid sample ratio: %f", sampleRatio)
}
setupCtx := ctx.Context
if setupCtx == nil {
setupCtx = context.Background()
}
// Configure OpenTelemetry tracing
handle, err := config.Setup(
setupCtx,
endpoint,
sampleRatio,
)
if err != nil {
return nil, err
}
log.Info("Telemetry enabled", "endpoint", endpoint)
return &telemetryService{telemetryProvider: handle}, nil
}
// RegisterTelemetryService registers the telemetryService with the node.
func RegisterTelemetryService(service *telemetryService, stack *node.Node) {
stack.RegisterLifecycle(service)
}

View file

@ -14,15 +14,18 @@
// You should have received a copy of the GNU Lesser General Public License // You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package config package tracesetup
import ( import (
"context" "context"
"encoding/base64"
"fmt" "fmt"
"net/url" "net/url"
"time" "time"
"github.com/ethereum/go-ethereum/internal/version" "github.com/ethereum/go-ethereum/internal/version"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
@ -32,22 +35,42 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.38.0" semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
) )
// TelemetryProvider wraps a TracerProvider and exposes the Shutdown method. // Service wraps the provider to implement node.Lifecycle.
type TelemetryProvider struct { type Service struct {
tracerProvider *sdktrace.TracerProvider provider *sdktrace.TracerProvider
} }
// Shutdown shuts down the TracerProvider. // Start implements node.Lifecycle.
func (t *TelemetryProvider) Shutdown(ctx context.Context) error { func (t *Service) Start() error {
return t.tracerProvider.Shutdown(ctx) return nil // provider is already started during setup
} }
// Setup initializes telemetry with the given parameters. // Stop implements node.Lifecycle.
func Setup(ctx context.Context, endpoint string, sampleRatio float64) (*TelemetryProvider, error) { func (t *Service) 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 OpenTelemetry service", "err", err)
return err
}
log.Debug("OpenTelemetry stopped")
return nil
}
// StartTelemetry initializes telemetry with the given parameters.
func StartTelemetry(ctx context.Context, cfg node.OpenTelemetryConfig, stack *node.Node) error {
if !cfg.Enabled {
return nil
}
if cfg.SampleRatio < 0 || cfg.SampleRatio > 1 {
return fmt.Errorf("invalid sample ratio: %f", cfg.SampleRatio)
}
// Create exporter based on endpoint URL // Create exporter based on endpoint URL
u, err := url.Parse(endpoint) u, err := url.Parse(cfg.Endpoint)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid rpc tracing endpoint URL: %w", err) return fmt.Errorf("invalid rpc tracing endpoint URL: %w", err)
} }
var exporter sdktrace.SpanExporter var exporter sdktrace.SpanExporter
switch u.Scheme { switch u.Scheme {
@ -61,18 +84,23 @@ func Setup(ctx context.Context, endpoint string, sampleRatio float64) (*Telemetr
if u.Path != "" && u.Path != "/" { if u.Path != "" && u.Path != "/" {
opts = append(opts, otlptracehttp.WithURLPath(u.Path)) opts = append(opts, otlptracehttp.WithURLPath(u.Path))
} }
if cfg.AuthUser != "" {
opts = append(opts, otlptracehttp.WithHeaders(map[string]string{
"Authorization": "Basic " + base64.StdEncoding.EncodeToString([]byte(cfg.AuthUser+":"+cfg.AuthPassword)),
}))
}
exporter, err = otlptracehttp.New(ctx, opts...) exporter, err = otlptracehttp.New(ctx, opts...)
default: default:
return nil, fmt.Errorf("unsupported telemetry url scheme: %s", u.Scheme) return fmt.Errorf("unsupported telemetry url scheme: %s", u.Scheme)
} }
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create telemetry exporter: %w", err) return fmt.Errorf("failed to create telemetry exporter: %w", err)
} }
// Define sampler such that if no parent span is available, // Define sampler such that if no parent span is available,
// then sampleRatio of traces are sampled; otherwise, inherit // then sampleRatio of traces are sampled; otherwise, inherit
// the parent's sampling decision. // the parent's sampling decision.
sampler := sdktrace.ParentBased(sdktrace.TraceIDRatioBased(sampleRatio)) sampler := sdktrace.ParentBased(sdktrace.TraceIDRatioBased(cfg.SampleRatio))
// Define batch span processor options // Define batch span processor options
batchOpts := []sdktrace.BatchSpanProcessorOption{ batchOpts := []sdktrace.BatchSpanProcessorOption{
@ -87,11 +115,14 @@ func Setup(ctx context.Context, endpoint string, sampleRatio float64) (*Telemetr
} }
// Define resource with service and client information // Define resource with service and client information
res := resource.NewWithAttributes( var attr = []attribute.KeyValue{
semconv.SchemaURL,
semconv.ServiceName("geth"), semconv.ServiceName("geth"),
attribute.String("client.name", version.ClientName("geth")), attribute.String("client.name", version.ClientName("geth")),
) }
if cfg.InstanceID != "" {
attr = append(attr, semconv.ServiceInstanceID(cfg.InstanceID))
}
res := resource.NewWithAttributes(semconv.SchemaURL, attr...)
// Configure TracerProvider and set it as the global tracer provider // Configure TracerProvider and set it as the global tracer provider
tp := sdktrace.NewTracerProvider( tp := sdktrace.NewTracerProvider(
@ -107,6 +138,8 @@ func Setup(ctx context.Context, endpoint string, sampleRatio float64) (*Telemetr
propagation.TraceContext{}, propagation.TraceContext{},
propagation.Baggage{}, propagation.Baggage{},
)) ))
service := &Service{provider: tp}
return &TelemetryProvider{tracerProvider: tp}, nil stack.RegisterLifecycle(service)
log.Info("OpenTelemetry tracing enabled", "endpoint", cfg.Endpoint)
return nil
} }

View file

@ -191,9 +191,7 @@ type Config struct {
GraphQLVirtualHosts []string `toml:",omitempty"` GraphQLVirtualHosts []string `toml:",omitempty"`
// Logger is a custom logger to use with the p2p.Server. // Logger is a custom logger to use with the p2p.Server.
Logger log.Logger `toml:",omitempty"` Logger log.Logger `toml:"-,omitempty"`
oldGethResourceWarning bool
// AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC. // AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC.
AllowUnprotectedTxs bool `toml:",omitempty"` AllowUnprotectedTxs bool `toml:",omitempty"`
@ -210,7 +208,28 @@ type Config struct {
// EnablePersonal enables the deprecated personal namespace. // EnablePersonal enables the deprecated personal namespace.
EnablePersonal bool `toml:"-"` EnablePersonal bool `toml:"-"`
// Configures database engine used by the node.
DBEngine string `toml:",omitempty"` DBEngine string `toml:",omitempty"`
// Configures OpenTelemetry reporting.
OpenTelemetry OpenTelemetryConfig `toml:",omitempty"`
oldGethResourceWarning bool
}
// OpenTelemetryConfig has settings for
type OpenTelemetryConfig struct {
Enabled bool `toml:",omitempty"`
InstanceID string `toml:",omitempty"`
// Exporter endpoint.
Endpoint string `toml:",omitempty"`
AuthUser string `toml:",omitempty"`
AuthPassword string `toml:",omitempty"`
// Percentage of sampled traces.
SampleRatio float64 `toml:",omitempty"`
} }
// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into // IPCEndpoint resolves an IPC endpoint based on a configured value, taking into