diff --git a/cmd/geth/config.go b/cmd/geth/config.go
index 5cd17e2f4c..c63933f658 100644
--- a/cmd/geth/config.go
+++ b/cmd/geth/config.go
@@ -39,6 +39,7 @@ import (
"github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"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/log"
"github.com/ethereum/go-ethereum/metrics"
@@ -239,18 +240,15 @@ func makeFullNode(ctx *cli.Context) *node.Node {
cfg.Eth.OverrideVerkle = &v
}
- // Start metrics export if enabled
+ // Start metrics export if enabled.
utils.SetupMetrics(&cfg.Metrics)
- // Setup telemetry if enabled
- telemetryService, err := utils.SetupTelemetry(ctx)
- if err != nil {
- utils.Fatalf("failed to setup telemetry: %v", err)
- }
- if telemetryService != nil {
- utils.RegisterTelemetryService(telemetryService, stack)
+ // Setup OpenTelemetry reporting if enabled.
+ if err := tracesetup.StartTelemetry(ctx.Context, cfg.Node.OpenTelemetry, stack); err != nil {
+ utils.Fatalf("failed to setup OpenTelemetry: %v", err)
}
+ // Add Ethereum service.
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// Create gauge with geth system and build information
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 0c0e066abb..e59f5aebfc 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -198,6 +198,9 @@ var (
utils.RPCGlobalRangeLimitFlag,
utils.RPCTelemetryFlag,
utils.RPCTelemetryEndpointFlag,
+ utils.RPCTelemetryUserFlag,
+ utils.RPCTelemetryPasswordFlag,
+ utils.RPCTelemetryInstanceIDFlag,
utils.RPCTelemetrySampleRatioFlag,
}
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index 8a252c53c3..73eca936cd 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -1056,6 +1056,24 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
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{
Name: "rpc.telemetry.sample-ratio",
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)
SetDataDir(ctx, cfg)
setSmartCard(ctx, cfg)
+ setOpenTelemetry(ctx, cfg)
if ctx.IsSet(JWTSecretFlag.Name) {
cfg.JWTSecret = ctx.String(JWTSecretFlag.Name)
@@ -1519,6 +1538,33 @@ func setSmartCard(ctx *cli.Context, cfg *node.Config) {
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) {
switch {
case ctx.IsSet(DataDirFlag.Name):
diff --git a/cmd/utils/telemetry.go b/cmd/utils/telemetry.go
deleted file mode 100644
index 2c2cd7a4bb..0000000000
--- a/cmd/utils/telemetry.go
+++ /dev/null
@@ -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 .
-
-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)
-}
diff --git a/internal/telemetry/config/config.go b/internal/telemetry/tracesetup/setup.go
similarity index 62%
rename from internal/telemetry/config/config.go
rename to internal/telemetry/tracesetup/setup.go
index 99bcbd87f0..e48bce9283 100644
--- a/internal/telemetry/config/config.go
+++ b/internal/telemetry/tracesetup/setup.go
@@ -14,15 +14,18 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-package config
+package tracesetup
import (
"context"
+ "encoding/base64"
"fmt"
"net/url"
"time"
"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/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
@@ -32,22 +35,42 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.38.0"
)
-// TelemetryProvider wraps a TracerProvider and exposes the Shutdown method.
-type TelemetryProvider struct {
- tracerProvider *sdktrace.TracerProvider
+// Service wraps the provider to implement node.Lifecycle.
+type Service struct {
+ provider *sdktrace.TracerProvider
}
-// Shutdown shuts down the TracerProvider.
-func (t *TelemetryProvider) Shutdown(ctx context.Context) error {
- return t.tracerProvider.Shutdown(ctx)
+// Start implements node.Lifecycle.
+func (t *Service) Start() error {
+ return nil // provider is already started during setup
}
-// Setup initializes telemetry with the given parameters.
-func Setup(ctx context.Context, endpoint string, sampleRatio float64) (*TelemetryProvider, error) {
+// Stop implements node.Lifecycle.
+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
- u, err := url.Parse(endpoint)
+ u, err := url.Parse(cfg.Endpoint)
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
switch u.Scheme {
@@ -61,18 +84,23 @@ func Setup(ctx context.Context, endpoint string, sampleRatio float64) (*Telemetr
if u.Path != "" && 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...)
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 {
- 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,
// then sampleRatio of traces are sampled; otherwise, inherit
// the parent's sampling decision.
- sampler := sdktrace.ParentBased(sdktrace.TraceIDRatioBased(sampleRatio))
+ sampler := sdktrace.ParentBased(sdktrace.TraceIDRatioBased(cfg.SampleRatio))
// Define batch span processor options
batchOpts := []sdktrace.BatchSpanProcessorOption{
@@ -87,11 +115,14 @@ func Setup(ctx context.Context, endpoint string, sampleRatio float64) (*Telemetr
}
// Define resource with service and client information
- res := resource.NewWithAttributes(
- semconv.SchemaURL,
+ var attr = []attribute.KeyValue{
semconv.ServiceName("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
tp := sdktrace.NewTracerProvider(
@@ -107,6 +138,8 @@ func Setup(ctx context.Context, endpoint string, sampleRatio float64) (*Telemetr
propagation.TraceContext{},
propagation.Baggage{},
))
-
- return &TelemetryProvider{tracerProvider: tp}, nil
+ service := &Service{provider: tp}
+ stack.RegisterLifecycle(service)
+ log.Info("OpenTelemetry tracing enabled", "endpoint", cfg.Endpoint)
+ return nil
}
diff --git a/node/config.go b/node/config.go
index dc436876cc..5e0ec63ee5 100644
--- a/node/config.go
+++ b/node/config.go
@@ -191,9 +191,7 @@ type Config struct {
GraphQLVirtualHosts []string `toml:",omitempty"`
// Logger is a custom logger to use with the p2p.Server.
- Logger log.Logger `toml:",omitempty"`
-
- oldGethResourceWarning bool
+ Logger log.Logger `toml:"-,omitempty"`
// AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC.
AllowUnprotectedTxs bool `toml:",omitempty"`
@@ -210,7 +208,28 @@ type Config struct {
// EnablePersonal enables the deprecated personal namespace.
EnablePersonal bool `toml:"-"`
+ // Configures database engine used by the node.
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