diff --git a/.github/workflows/beekeeper.yml b/.github/workflows/beekeeper.yml index 0cb7c41fe2f..57ce596b21f 100644 --- a/.github/workflows/beekeeper.yml +++ b/.github/workflows/beekeeper.yml @@ -7,8 +7,8 @@ on: paths-ignore: - packaging/** - openapi/** - - '**/*.md' - - '.github/ISSUE_TEMPLATE/**' + - "**/*.md" + - ".github/ISSUE_TEMPLATE/**" branches: - "**" @@ -19,7 +19,7 @@ env: SETUP_CONTRACT_IMAGE: "ethersphere/bee-localchain" SETUP_CONTRACT_IMAGE_TAG: "0.9.4" BEELOCAL_BRANCH: "main" - BEEKEEPER_BRANCH: "master" + BEEKEEPER_BRANCH: "feat/bee-otlp-tracing-config" BEEKEEPER_METRICS_ENABLED: false REACHABILITY_OVERRIDE_PUBLIC: true BATCHFACTOR_OVERRIDE_PUBLIC: 2 diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index b4ece436d8d..4cdd4abeda8 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -40,9 +40,11 @@ const ( optionWelcomeMessage = "welcome-message" optionCORSAllowedOrigins = "cors-allowed-origins" optionNameTracingEnabled = "tracing-enable" - optionNameTracingEndpoint = "tracing-endpoint" - optionNameTracingHost = "tracing-host" - optionNameTracingPort = "tracing-port" + optionNameTracingOTLPEndpoint = "tracing-otlp-endpoint" + optionNameTracingOTLPInsecure = "tracing-otlp-insecure" + optionNameTracingOTLPCAFile = "tracing-otlp-ca-file" + optionNameTracingOTLPProtocol = "tracing-otlp-protocol" + optionNameTracingSamplingRatio = "tracing-sampling-ratio" optionNameTracingServiceName = "tracing-service-name" optionNameVerbosity = "verbosity" optionNamePaymentThreshold = "payment-threshold" @@ -100,6 +102,15 @@ const ( configKeyBlockchainRpcTLSTimeout = "blockchain-rpc.tls-timeout" configKeyBlockchainRpcIdleTimeout = "blockchain-rpc.idle-timeout" configKeyBlockchainRpcKeepalive = "blockchain-rpc.keepalive" + + // tracing + configKeyTracingEnabled = "tracing.enable" + configKeyTracingOTLPEndpoint = "tracing.otlp-endpoint" + configKeyTracingOTLPInsecure = "tracing.otlp-insecure" + configKeyTracingOTLPCAFile = "tracing.otlp-ca-file" + configKeyTracingOTLPProtocol = "tracing.otlp-protocol" + configKeyTracingSamplingRatio = "tracing.sampling-ratio" + configKeyTracingServiceName = "tracing.service-name" ) var blockchainRpcConfigPairs = []struct{ flat, dotted string }{ @@ -110,11 +121,24 @@ var blockchainRpcConfigPairs = []struct{ flat, dotted string }{ {optionNameBlockchainRpcKeepalive, configKeyBlockchainRpcKeepalive}, } +var tracingConfigPairs = []struct{ flat, dotted string }{ + {optionNameTracingEnabled, configKeyTracingEnabled}, + {optionNameTracingOTLPEndpoint, configKeyTracingOTLPEndpoint}, + {optionNameTracingOTLPInsecure, configKeyTracingOTLPInsecure}, + {optionNameTracingOTLPCAFile, configKeyTracingOTLPCAFile}, + {optionNameTracingOTLPProtocol, configKeyTracingOTLPProtocol}, + {optionNameTracingSamplingRatio, configKeyTracingSamplingRatio}, + {optionNameTracingServiceName, configKeyTracingServiceName}, +} + var knownNestedKeys = func() map[string]bool { - m := make(map[string]bool, len(blockchainRpcConfigPairs)) + m := make(map[string]bool, len(blockchainRpcConfigPairs)+len(tracingConfigPairs)) for _, p := range blockchainRpcConfigPairs { m[p.dotted] = true } + for _, p := range tracingConfigPairs { + m[p.dotted] = true + } return m }() @@ -281,9 +305,11 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().Uint64(optionNameNetworkID, chaincfg.Mainnet.NetworkID, "ID of the Swarm network") cmd.Flags().StringSlice(optionCORSAllowedOrigins, []string{}, "origins with CORS headers enabled") cmd.Flags().Bool(optionNameTracingEnabled, false, "enable tracing") - cmd.Flags().String(optionNameTracingEndpoint, "127.0.0.1:6831", "endpoint to send tracing data") - cmd.Flags().String(optionNameTracingHost, "", "host to send tracing data") - cmd.Flags().String(optionNameTracingPort, "", "port to send tracing data") + cmd.Flags().String(optionNameTracingOTLPEndpoint, "127.0.0.1:4318", "OTLP endpoint to send tracing data (host:port); default port is 4318 for http, 4317 for grpc") + cmd.Flags().Bool(optionNameTracingOTLPInsecure, false, "disable TLS for the OTLP exporter (useful for a local collector); when false, set --tracing-otlp-ca-file to verify the collector certificate against a private CA") + cmd.Flags().String(optionNameTracingOTLPCAFile, "", "path to a PEM-encoded CA bundle used to verify the OTLP collector certificate; ignored when --tracing-otlp-insecure=true") + cmd.Flags().String(optionNameTracingOTLPProtocol, "http", "OTLP exporter transport: http or grpc") + cmd.Flags().Float64(optionNameTracingSamplingRatio, 1.0, "head-based sampling ratio in [0,1]; 0 samples nothing, 1 samples everything") cmd.Flags().String(optionNameTracingServiceName, "bee", "service name identifier for tracing") cmd.Flags().String(optionNameVerbosity, "info", "log verbosity level 0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=trace") cmd.Flags().String(optionWelcomeMessage, "", "send a welcome message string during handshakes") @@ -364,7 +390,17 @@ func (c *command) initLogger(cmd *cobra.Command) error { // bindBlockchainRpcConfig supports both flat (blockchain-rpc-endpoint) and // nested (blockchain-rpc.endpoint) YAML forms, with nested taking precedence. func (c *command) bindBlockchainRpcConfig(cmd *cobra.Command) { - for _, p := range blockchainRpcConfigPairs { + c.bindNestedConfig(cmd, blockchainRpcConfigPairs) +} + +// bindTracingConfig supports both flat (tracing-otlp-endpoint) and nested +// (tracing.otlp-endpoint) YAML forms, with nested taking precedence. +func (c *command) bindTracingConfig(cmd *cobra.Command) { + c.bindNestedConfig(cmd, tracingConfigPairs) +} + +func (c *command) bindNestedConfig(cmd *cobra.Command, pairs []struct{ flat, dotted string }) { + for _, p := range pairs { // Check before registering the alias; afterwards the flat value is unreachable. if c.config.InConfig(p.flat) && c.config.InConfig(p.dotted) { c.logger.Warning("config key conflict: nested form takes precedence", "ignored", p.flat, "used", p.dotted) diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 5773c4af3e3..6dc66a29553 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -15,7 +15,6 @@ import ( "os" "os/signal" "path/filepath" - "strings" "sync/atomic" "syscall" "time" @@ -159,6 +158,7 @@ func (c *command) initStartCmd() (err error) { return err } c.bindBlockchainRpcConfig(cmd) + c.bindTracingConfig(cmd) return nil }, } @@ -241,12 +241,6 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo networkConfig.blockTime = time.Duration(blockTime) * time.Second } - tracingEndpoint := c.config.GetString(optionNameTracingEndpoint) - - if c.config.IsSet(optionNameTracingHost) && c.config.IsSet(optionNameTracingPort) { - tracingEndpoint = strings.Join([]string{c.config.GetString(optionNameTracingHost), c.config.GetString(optionNameTracingPort)}, ":") - } - staticNodesOpt := c.config.GetStringSlice(optionNameStaticNodes) staticNodes := make([]swarm.Address, 0, len(staticNodesOpt)) for _, p := range staticNodesOpt { @@ -325,9 +319,13 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo SwapFactoryAddress: c.config.GetString(optionNameSwapFactoryAddress), SwapInitialDeposit: c.config.GetString(optionNameSwapInitialDeposit), TargetNeighborhood: c.config.GetString(optionNameTargetNeighborhood), - TracingEnabled: c.config.GetBool(optionNameTracingEnabled), - TracingEndpoint: tracingEndpoint, - TracingServiceName: c.config.GetString(optionNameTracingServiceName), + TracingEnabled: c.config.GetBool(configKeyTracingEnabled), + TracingEndpoint: c.config.GetString(configKeyTracingOTLPEndpoint), + TracingInsecure: c.config.GetBool(configKeyTracingOTLPInsecure), + TracingCAFile: c.config.GetString(configKeyTracingOTLPCAFile), + TracingProtocol: c.config.GetString(configKeyTracingOTLPProtocol), + TracingSamplingRatio: c.config.GetFloat64(configKeyTracingSamplingRatio), + TracingServiceName: c.config.GetString(configKeyTracingServiceName), TrxDebugMode: c.config.GetBool(optionNameTransactionDebugMode), WarmupTime: c.config.GetDuration(optionWarmUpTime), WelcomeMessage: c.config.GetString(optionWelcomeMessage), diff --git a/go.mod b/go.mod index 6885b1805d0..c2b60091fdc 100644 --- a/go.mod +++ b/go.mod @@ -32,26 +32,31 @@ require ( github.com/multiformats/go-multiaddr-dns v0.4.1 github.com/multiformats/go-multihash v0.2.3 github.com/multiformats/go-multistream v0.6.1 - github.com/opentracing/opentracing-go v1.2.0 github.com/prometheus/client_golang v1.22.0 github.com/spf13/afero v1.6.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.11.1 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/uber/jaeger-client-go v2.24.0+incompatible github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/wealdtech/go-ens/v3 v3.5.1 gitlab.com/nolash/go-mockbytes v0.0.7 + go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 + go.opentelemetry.io/otel/sdk v1.43.0 + go.opentelemetry.io/otel/trace v1.43.0 go.uber.org/atomic v1.11.0 go.uber.org/goleak v1.3.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.48.0 - golang.org/x/net v0.50.0 - golang.org/x/sync v0.19.0 - golang.org/x/sys v0.41.0 - golang.org/x/term v0.40.0 + golang.org/x/crypto v0.49.0 + golang.org/x/net v0.52.0 + golang.org/x/sync v0.20.0 + golang.org/x/sys v0.42.0 + golang.org/x/term v0.41.0 golang.org/x/time v0.12.0 + google.golang.org/grpc v1.80.0 gopkg.in/yaml.v2 v2.4.0 resenje.org/feed v0.1.2 resenje.org/multex v0.1.0 @@ -59,6 +64,18 @@ require ( resenje.org/web v0.4.3 ) +require ( + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 // indirect +) + require ( filippo.io/bigmod v0.1.1-0.20260103110540-f8a47775ebe5 // indirect filippo.io/keygen v0.0.0-20260114151900-8e2790ea4c5b // indirect @@ -72,7 +89,6 @@ require ( github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect github.com/caddyserver/zerossl v0.1.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/codahale/hdrhistogram v0.0.0-00010101000000-000000000000 // indirect github.com/consensys/gnark-crypto v0.18.1 // indirect github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect @@ -160,7 +176,6 @@ require ( github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/quic-go/webtransport-go v0.10.0 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/shirou/gopsutil v3.21.5+incompatible // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/cast v1.3.0 // indirect @@ -170,7 +185,6 @@ require ( github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - github.com/uber/jaeger-lib v2.2.0+incompatible // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/wealdtech/go-multicodec v1.4.0 // indirect github.com/wlynxg/anet v0.0.5 // indirect @@ -182,11 +196,11 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect golang.org/x/exp v0.0.0-20250606033433-dcc06ee1d476 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 // indirect - golang.org/x/text v0.34.0 // indirect - golang.org/x/tools v0.41.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 // indirect + golang.org/x/text v0.35.0 // indirect + golang.org/x/tools v0.42.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.4.1 // indirect diff --git a/go.sum b/go.sum index d0fc255af2a..168e32328ca 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,6 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= -github.com/HdrHistogram/hdrhistogram-go v0.0.0-20200919145931-8dac23c8dac1 h1:nEjGZtKHMK92888VT6XkzKwyiW14v5FFRGeWq2uV7N0= -github.com/HdrHistogram/hdrhistogram-go v0.0.0-20200919145931-8dac23c8dac1/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -148,6 +146,8 @@ github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+Y github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3 h1:oe6fCvaEpkhyW3qAicT0TnGtyht/UrgvOwMcEgLb7Aw= github.com/canonical/go-sp800.90a-drbg v0.0.0-20210314144037-6eeb1040d6c3/go.mod h1:qdP0gaj0QtgX2RUZhnlVrceJ+Qln8aSlDyJwelLLFeM= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/cp v1.1.1 h1:nCb6ZLdB7NRaqsm91JtQTAme2SKJzXVsdPIPkyJr1MU= @@ -302,6 +302,11 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= @@ -363,6 +368,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -429,6 +436,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -856,8 +865,8 @@ github.com/rjeczalik/notify v0.9.1/go.mod h1:rKwnCoCGeuQnwBtTSPL9Dad03Vh2n40ePRr github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= @@ -963,10 +972,6 @@ github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9f github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= -github.com/uber/jaeger-client-go v2.24.0+incompatible h1:CGchgJcHsDd2jWnaL4XngByMrXoGHh3n8oCqAKx0uMo= -github.com/uber/jaeger-client-go v2.24.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= -github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= @@ -1011,6 +1016,26 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -1056,8 +1081,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1096,8 +1121,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1148,8 +1173,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1172,8 +1197,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1246,15 +1271,15 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2 h1:O1cMQHRfwNpDfDJerqRoE2oD+AFlyid87D40L/OkkJo= -golang.org/x/telemetry v0.0.0-20260109210033-bd525da824e2/go.mod h1:b7fPSJ0pKZ3ccUh8gnTONJxhn3c/PS6tyzQvyqw4iA8= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4 h1:bTLqdHv7xrGlFbvf5/TXNxy/iUwwdkjhqQTJDjW7aj0= +golang.org/x/telemetry v0.0.0-20260209163413-e7419c687ee4/go.mod h1:g5NllXBEermZrmR51cJDQxmJUHUOfRAaNyWBM+R+548= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1263,8 +1288,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1324,8 +1349,8 @@ golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1334,6 +1359,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.0.0-20181121035319-3f7ecaa7e8ca/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.6.0/go.mod h1:9mxDZsDKxgMAuccQkewq682L+0eCu4dCN2yonUJTCLU= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= @@ -1399,6 +1426,10 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348 h1:U8orV30l6KpDsi9dxU0CoJZGbjS8EEpw+6ba+XwGPQA= +google.golang.org/genproto/googleapis/api v0.0.0-20260504160031-60b97b32f348/go.mod h1:Yzdzr5OOZFgSsEV2D/Xi9NL3bszpXFAg0hFJiRohcD8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348 h1:pfIbyB44sWzHiCpRqIen67ZQnVXSfIxWrqUMk1qwODE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260504160031-60b97b32f348/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -1415,6 +1446,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1429,8 +1462,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/packaging/bee.yaml b/packaging/bee.yaml index ef04fb716c6..c71393a88fe 100644 --- a/packaging/bee.yaml +++ b/packaging/bee.yaml @@ -105,16 +105,22 @@ password-file: "/var/lib/bee/password" # swap-initial-deposit: "0" ## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay # target-neighborhood: "" -## enable tracing -# tracing-enable: false -## endpoint to send tracing data -# tracing-endpoint: 127.0.0.1:6831 -## host to send tracing data -# tracing-host: "" -## port to send tracing data -# tracing-port: "" -## service name identifier for tracing -# tracing-service-name: bee +## tracing settings +# tracing: +# ## enable tracing +# enable: false +# ## OTLP endpoint to send tracing data (host:port); default port is 4318 for http, 4317 for grpc +# otlp-endpoint: 127.0.0.1:4318 +# ## disable TLS for the OTLP exporter (useful for a local collector) +# otlp-insecure: false +# ## path to a PEM-encoded CA bundle used to verify the OTLP collector certificate; ignored when otlp-insecure is true +# otlp-ca-file: "" +# ## OTLP exporter transport: http or grpc +# otlp-protocol: http +# ## head-based sampling ratio in [0,1]; 0 samples nothing, 1 samples everything +# sampling-ratio: 1.0 +# ## service name identifier for tracing +# service-name: bee ## gas limit fallback when estimation fails for contract transactions (default 500000) # gas-limit-fallback: 500000 ## skips the gas estimate step for contract transactions diff --git a/packaging/homebrew-amd64/bee.yaml b/packaging/homebrew-amd64/bee.yaml index 2aac12016fe..97a6910d8ab 100644 --- a/packaging/homebrew-amd64/bee.yaml +++ b/packaging/homebrew-amd64/bee.yaml @@ -105,16 +105,22 @@ password-file: "/usr/local/var/lib/swarm-bee/password" # swap-initial-deposit: "0" ## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay # target-neighborhood: "" -## enable tracing -# tracing-enable: false -## endpoint to send tracing data -# tracing-endpoint: 127.0.0.1:6831 -## host to send tracing data -# tracing-host: "" -## port to send tracing data -# tracing-port: "" -## service name identifier for tracing -# tracing-service-name: bee +## tracing settings +# tracing: +# ## enable tracing +# enable: false +# ## OTLP endpoint to send tracing data (host:port); default port is 4318 for http, 4317 for grpc +# otlp-endpoint: 127.0.0.1:4318 +# ## disable TLS for the OTLP exporter (useful for a local collector) +# otlp-insecure: false +# ## path to a PEM-encoded CA bundle used to verify the OTLP collector certificate; ignored when otlp-insecure is true +# otlp-ca-file: "" +# ## OTLP exporter transport: http or grpc +# otlp-protocol: http +# ## head-based sampling ratio in [0,1]; 0 samples nothing, 1 samples everything +# sampling-ratio: 1.0 +# ## service name identifier for tracing +# service-name: bee ## gas limit fallback when estimation fails for contract transactions (default 500000) # gas-limit-fallback: 500000 ## skips the gas estimate step for contract transactions diff --git a/packaging/homebrew-arm64/bee.yaml b/packaging/homebrew-arm64/bee.yaml index f812f395f07..d24d17b5848 100644 --- a/packaging/homebrew-arm64/bee.yaml +++ b/packaging/homebrew-arm64/bee.yaml @@ -105,16 +105,22 @@ password-file: "/opt/homebrew/var/lib/swarm-bee/password" # swap-initial-deposit: "0" ## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay # target-neighborhood: "" -## enable tracing -# tracing-enable: false -## endpoint to send tracing data -# tracing-endpoint: 127.0.0.1:6831 -## host to send tracing data -# tracing-host: "" -## port to send tracing data -# tracing-port: "" -## service name identifier for tracing -# tracing-service-name: bee +## tracing settings +# tracing: +# ## enable tracing +# enable: false +# ## OTLP endpoint to send tracing data (host:port); default port is 4318 for http, 4317 for grpc +# otlp-endpoint: 127.0.0.1:4318 +# ## disable TLS for the OTLP exporter (useful for a local collector) +# otlp-insecure: false +# ## path to a PEM-encoded CA bundle used to verify the OTLP collector certificate; ignored when otlp-insecure is true +# otlp-ca-file: "" +# ## OTLP exporter transport: http or grpc +# otlp-protocol: http +# ## head-based sampling ratio in [0,1]; 0 samples nothing, 1 samples everything +# sampling-ratio: 1.0 +# ## service name identifier for tracing +# service-name: bee ## gas limit fallback when estimation fails for contract transactions (default 500000) # gas-limit-fallback: 500000 ## skips the gas estimate step for contract transactions diff --git a/packaging/scoop/bee.yaml b/packaging/scoop/bee.yaml index f44aaf0e0d0..a3d2e5fd107 100644 --- a/packaging/scoop/bee.yaml +++ b/packaging/scoop/bee.yaml @@ -105,16 +105,22 @@ password-file: "./password" # swap-initial-deposit: "0" ## neighborhood to target in binary format (ex: 111111001) for mining the initial overlay # target-neighborhood: "" -## enable tracing -# tracing-enable: false -## endpoint to send tracing data -# tracing-endpoint: 127.0.0.1:6831 -## host to send tracing data -# tracing-host: "" -## port to send tracing data -# tracing-port: "" -## service name identifier for tracing -# tracing-service-name: bee +## tracing settings +# tracing: +# ## enable tracing +# enable: false +# ## OTLP endpoint to send tracing data (host:port); default port is 4318 for http, 4317 for grpc +# otlp-endpoint: 127.0.0.1:4318 +# ## disable TLS for the OTLP exporter (useful for a local collector) +# otlp-insecure: false +# ## path to a PEM-encoded CA bundle used to verify the OTLP collector certificate; ignored when otlp-insecure is true +# otlp-ca-file: "" +# ## OTLP exporter transport: http or grpc +# otlp-protocol: http +# ## head-based sampling ratio in [0,1]; 0 samples nothing, 1 samples everything +# sampling-ratio: 1.0 +# ## service name identifier for tracing +# service-name: bee ## gas limit fallback when estimation fails for contract transactions (default 500000) # gas-limit-fallback: 500000 ## skips the gas estimate step for contract transactions diff --git a/pkg/api/api.go b/pkg/api/api.go index acd838a3ff6..83be07ecaeb 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -463,7 +463,7 @@ func (s *Service) newTracingHandler(spanName string) func(h http.Handler) http.H } span, _, ctx := s.tracer.StartSpanFromContext(ctx, spanName, s.logger) - defer span.Finish() + defer span.End() err = s.tracer.AddContextHTTPHeader(ctx, r.Header) if err != nil { diff --git a/pkg/api/bytes.go b/pkg/api/bytes.go index 314e39fcb56..6cae0511f97 100644 --- a/pkg/api/bytes.go +++ b/pkg/api/bytes.go @@ -21,8 +21,7 @@ import ( "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/tracing" "github.com/gorilla/mux" - "github.com/opentracing/opentracing-go/ext" - olog "github.com/opentracing/opentracing-go/log" + "go.opentelemetry.io/otel/attribute" ) type bytesPostResponse struct { @@ -32,7 +31,7 @@ type bytesPostResponse struct { // bytesUploadHandler handles upload of raw binary data of arbitrary length. func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { span, logger, ctx := s.tracer.StartSpanFromContext(r.Context(), "post_bytes", s.logger.WithName("post_bytes").Build()) - defer span.Finish() + defer span.End() headers := struct { BatchID []byte `map:"Swarm-Postage-Batch-Id" validate:"required"` @@ -66,10 +65,10 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { default: jsonhttp.InternalServerError(w, "cannot get or create tag") } - ext.LogError(span, err, olog.String("action", "tag.create")) + tracing.RecordError(span, err, attribute.String("action", "tag.create")) return } - span.SetTag("tagID", tag) + span.SetAttributes(attribute.Int64("tagID", int64(tag))) } defer s.observeUploadSpeed(w, r, time.Now(), "bytes", deferred) @@ -93,7 +92,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { default: jsonhttp.BadRequest(w, nil) } - ext.LogError(span, err, olog.String("action", "new.StamperPutter")) + tracing.RecordError(span, err, attribute.String("action", "new.StamperPutter")) return } @@ -114,7 +113,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { default: jsonhttp.InternalServerError(ow, "split write all failed") } - ext.LogError(span, err, olog.String("action", "split.WriteAll")) + tracing.RecordError(span, err, attribute.String("action", "split.WriteAll")) return } @@ -138,14 +137,14 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { return } } - span.SetTag("root_address", encryptedReference) + span.SetAttributes(attribute.String("root_address", encryptedReference.String())) err = putter.Done(reference) if err != nil { logger.Debug("done split failed", "error", err) logger.Error(nil, "done split failed") jsonhttp.InternalServerError(ow, "done split failed") - ext.LogError(span, err, olog.String("action", "putter.Done")) + tracing.RecordError(span, err, attribute.String("action", "putter.Done")) return } @@ -153,7 +152,7 @@ func (s *Service) bytesUploadHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set(SwarmTagHeader, fmt.Sprint(tag)) } - span.LogFields(olog.Bool("success", true)) + span.SetAttributes(attribute.Bool("success", true)) w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader) if headers.Act { diff --git a/pkg/api/bzz.go b/pkg/api/bzz.go index a365212a4ec..2adb7744272 100644 --- a/pkg/api/bzz.go +++ b/pkg/api/bzz.go @@ -19,9 +19,8 @@ import ( "strings" "time" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - olog "github.com/opentracing/opentracing-go/log" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "github.com/ethereum/go-ethereum/common" "github.com/ethersphere/bee/v2/pkg/accesscontrol" @@ -67,7 +66,7 @@ func lookaheadBufferSize(size int64) int { func (s *Service) bzzUploadHandler(w http.ResponseWriter, r *http.Request) { span, logger, ctx := s.tracer.StartSpanFromContext(r.Context(), "post_bzz", s.logger.WithName("post_bzz").Build()) - defer span.Finish() + defer span.End() headers := struct { ContentType string `map:"Content-Type"` @@ -105,10 +104,10 @@ func (s *Service) bzzUploadHandler(w http.ResponseWriter, r *http.Request) { default: jsonhttp.InternalServerError(w, "cannot get or create tag") } - ext.LogError(span, err, olog.String("action", "tag.create")) + tracing.RecordError(span, err, attribute.String("action", "tag.create")) return } - span.SetTag("tagID", tag) + span.SetAttributes(attribute.Int64("tagID", int64(tag))) } putter, err := s.newStamperPutter(ctx, putterOptions{ @@ -130,7 +129,7 @@ func (s *Service) bzzUploadHandler(w http.ResponseWriter, r *http.Request) { default: jsonhttp.BadRequest(w, nil) } - ext.LogError(span, err, olog.String("action", "new.StamperPutter")) + tracing.RecordError(span, err, attribute.String("action", "new.StamperPutter")) return } @@ -170,7 +169,7 @@ type bzzUploadResponse struct { func (s *Service) fileUploadHandler( ctx context.Context, logger log.Logger, - span opentracing.Span, + span trace.Span, w http.ResponseWriter, r *http.Request, putter storer.PutterSession, @@ -217,7 +216,7 @@ func (s *Service) fileUploadHandler( default: jsonhttp.InternalServerError(w, errFileStore) } - ext.LogError(span, err, olog.String("action", "file.store")) + tracing.RecordError(span, err, attribute.String("action", "file.store")) return } @@ -326,15 +325,17 @@ func (s *Service) fileUploadHandler( logger.Debug("done split failed", "reference", manifestReference, "error", err) logger.Error(nil, "done split failed") jsonhttp.InternalServerError(w, "done split failed") - ext.LogError(span, err, olog.String("action", "putter.Done")) + tracing.RecordError(span, err, attribute.String("action", "putter.Done")) return } - span.LogFields(olog.Bool("success", true)) - span.SetTag("root_address", reference) + span.SetAttributes( + attribute.Bool("success", true), + attribute.String("root_address", reference.String()), + ) if tagID != 0 { w.Header().Set(SwarmTagHeader, fmt.Sprint(tagID)) - span.SetTag("tagID", tagID) + span.SetAttributes(attribute.Int64("tagID", int64(tagID))) } w.Header().Set(ETagHeader, fmt.Sprintf("%q", reference.String())) w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader) diff --git a/pkg/api/dirs.go b/pkg/api/dirs.go index ae0f15d23c0..3f2f128ab82 100644 --- a/pkg/api/dirs.go +++ b/pkg/api/dirs.go @@ -29,9 +29,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/storer" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/tracing" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - olog "github.com/opentracing/opentracing-go/log" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) var errEmptyDir = errors.New("no files in root directory") @@ -40,7 +39,7 @@ var errEmptyDir = errors.New("no files in root directory") func (s *Service) dirUploadHandler( ctx context.Context, logger log.Logger, - span opentracing.Span, + span trace.Span, w http.ResponseWriter, r *http.Request, putter storer.PutterSession, @@ -96,7 +95,7 @@ func (s *Service) dirUploadHandler( default: jsonhttp.InternalServerError(w, errDirectoryStore) } - ext.LogError(span, err, olog.String("action", "dir.store")) + tracing.RecordError(span, err, attribute.String("action", "dir.store")) return } @@ -126,13 +125,13 @@ func (s *Service) dirUploadHandler( logger.Debug("store dir failed", "error", err) logger.Error(nil, "store dir failed") jsonhttp.InternalServerError(w, errDirectoryStore) - ext.LogError(span, err, olog.String("action", "putter.Done")) + tracing.RecordError(span, err, attribute.String("action", "putter.Done")) return } if tag != 0 { w.Header().Set(SwarmTagHeader, fmt.Sprint(tag)) - span.LogFields(olog.Bool("success", true)) + span.SetAttributes(attribute.Bool("success", true)) } w.Header().Set(AccessControlExposeHeaders, SwarmTagHeader) if act { diff --git a/pkg/api/pingpong.go b/pkg/api/pingpong.go index 6cb1c4ae23e..1b8ebbecf4e 100644 --- a/pkg/api/pingpong.go +++ b/pkg/api/pingpong.go @@ -31,7 +31,7 @@ func (s *Service) pingpongHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() span, logger, ctx := s.tracer.StartSpanFromContext(ctx, "pingpong-api", logger) - defer span.Finish() + defer span.End() rtt, err := s.pingpong.Ping(ctx, paths.Address, "ping") if err != nil { diff --git a/pkg/node/node.go b/pkg/node/node.go index 35b78d4dc05..78c3bd53b3b 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -188,6 +188,10 @@ type Options struct { TargetNeighborhood string TracingEnabled bool TracingEndpoint string + TracingInsecure bool + TracingCAFile string + TracingProtocol string + TracingSamplingRatio float64 TracingServiceName string TrxDebugMode bool WarmupTime time.Duration @@ -230,9 +234,14 @@ func NewBee( nodeMetrics := newMetrics() tracer, tracerCloser, err := tracing.NewTracer(&tracing.Options{ - Enabled: o.TracingEnabled, - Endpoint: o.TracingEndpoint, - ServiceName: o.TracingServiceName, + Enabled: o.TracingEnabled, + Endpoint: o.TracingEndpoint, + Insecure: o.TracingInsecure, + CAFile: o.TracingCAFile, + Protocol: o.TracingProtocol, + SamplingRatio: o.TracingSamplingRatio, + ServiceName: o.TracingServiceName, + Logger: logger, }) if err != nil { return nil, fmt.Errorf("tracer: %w", err) diff --git a/pkg/p2p/libp2p/tracing_test.go b/pkg/p2p/libp2p/tracing_test.go index 66ebd22aa29..a41aed424c2 100644 --- a/pkg/p2p/libp2p/tracing_test.go +++ b/pkg/p2p/libp2p/tracing_test.go @@ -20,6 +20,8 @@ func TestTracing(t *testing.T) { tracer1, closer1, err := tracing.NewTracer(&tracing.Options{ Enabled: true, + Endpoint: "127.0.0.1:1", + Insecure: true, ServiceName: "bee-test", }) if err != nil { @@ -29,6 +31,8 @@ func TestTracing(t *testing.T) { tracer2, closer2, err := tracing.NewTracer(&tracing.Options{ Enabled: true, + Endpoint: "127.0.0.1:1", + Insecure: true, ServiceName: "bee-test", }) if err != nil { @@ -46,9 +50,9 @@ func TestTracing(t *testing.T) { handled := make(chan struct{}) if err := s1.AddProtocol(newTestProtocol(func(ctx context.Context, _ p2p.Peer, _ p2p.Stream) error { span, _, _ := tracer1.StartSpanFromContext(ctx, "test-p2p-handler", nil) - defer span.Finish() + defer span.End() - handledTracingSpan = fmt.Sprint(span.Context()) + handledTracingSpan = fmt.Sprint(span.SpanContext().TraceID()) close(handled) return nil })); err != nil { @@ -66,9 +70,9 @@ func TestTracing(t *testing.T) { ctx := t.Context() span, _, ctx := tracer2.StartSpanFromContext(ctx, "test-p2p-client", nil) - defer span.Finish() + defer span.End() - if fmt.Sprint(span.Context()) == "" { + if !span.SpanContext().IsValid() { t.Error("not tracing span context to send") } diff --git a/pkg/pingpong/pingpong.go b/pkg/pingpong/pingpong.go index d62db98b0ac..7c90fe04dac 100644 --- a/pkg/pingpong/pingpong.go +++ b/pkg/pingpong/pingpong.go @@ -65,7 +65,7 @@ func (s *Service) Protocol() p2p.ProtocolSpec { func (s *Service) Ping(ctx context.Context, address swarm.Address, msgs ...string) (rtt time.Duration, err error) { span, _, ctx := s.tracer.StartSpanFromContext(ctx, "pingpong-p2p-ping", s.logger) - defer span.Finish() + defer span.End() start := time.Now() stream, err := s.streamer.NewStream(ctx, address, nil, protocolName, protocolVersion, streamName) @@ -104,7 +104,7 @@ func (s *Service) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream) er defer stream.FullClose() span, _, ctx := s.tracer.StartSpanFromContext(ctx, "pingpong-p2p-handler", s.logger) - defer span.Finish() + defer span.End() var ping pb.Ping for { diff --git a/pkg/pusher/pusher.go b/pkg/pusher/pusher.go index 9c4073f1fcc..b756bb088d6 100644 --- a/pkg/pusher/pusher.go +++ b/pkg/pusher/pusher.go @@ -23,9 +23,9 @@ import ( "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/topology" "github.com/ethersphere/bee/v2/pkg/tracing" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - olog "github.com/opentracing/opentracing-go/log" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) // loggerName is the tree path name of the logger for this package. @@ -35,7 +35,7 @@ type Op struct { Chunk swarm.Chunk Err chan error Direct bool - Span opentracing.Span + Span trace.Span identityAddress swarm.Address } @@ -155,9 +155,9 @@ func (s *Service) chunksWorker(startupStabilizer stabilization.Subscriber) { spanCtx := ctx if op.Span != nil { - spanCtx = tracing.WithContext(spanCtx, op.Span.Context()) + spanCtx = tracing.WithContext(spanCtx, op.Span.SpanContext()) } else { - op.Span = opentracing.NoopTracer{}.StartSpan("noOp") + _, op.Span = noop.NewTracerProvider().Tracer(loggerName).Start(spanCtx, "noOp") } if op.Direct { @@ -169,9 +169,9 @@ func (s *Service) chunksWorker(startupStabilizer stabilization.Subscriber) { if err != nil { s.metrics.TotalErrors.Inc() s.metrics.ErrorTime.Observe(time.Since(startTime).Seconds()) - ext.LogError(op.Span, err) + tracing.RecordError(op.Span, err) } else { - op.Span.LogFields(olog.Bool("success", true)) + op.Span.SetAttributes(attribute.Bool("success", true)) } s.metrics.SyncTime.Observe(time.Since(startTime).Seconds()) diff --git a/pkg/pushsync/pushsync.go b/pkg/pushsync/pushsync.go index 05c4aef3d41..59a5e842afe 100644 --- a/pkg/pushsync/pushsync.go +++ b/pkg/pushsync/pushsync.go @@ -29,9 +29,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/topology" "github.com/ethersphere/bee/v2/pkg/tracing" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - olog "github.com/opentracing/opentracing-go/log" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "golang.org/x/time/rate" ) @@ -208,7 +207,11 @@ func (ps *PushSync) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream) chunk := swarm.NewChunk(swarm.NewAddress(ch.Address), ch.Data) chunkAddress := chunk.Address() - span, _, ctx := ps.tracer.StartSpanFromContext(ctx, "pushsync-handler", ps.logger, opentracing.Tag{Key: "address", Value: chunkAddress.String()}, opentracing.Tag{Key: "tagID", Value: chunk.TagID()}, opentracing.Tag{Key: "sender_address", Value: p.Address.String()}) + span, _, ctx := ps.tracer.StartSpanFromContext(ctx, "pushsync-handler", ps.logger, trace.WithAttributes( + attribute.String("address", chunkAddress.String()), + attribute.Int64("tagID", int64(chunk.TagID())), + attribute.String("sender_address", p.Address.String()), + )) var ( stored bool @@ -217,17 +220,18 @@ func (ps *PushSync) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream) defer func() { if err != nil { - ext.LogError(span, err) + tracing.RecordError(span, err) } else { - var logs []olog.Field - logs = append(logs, olog.Bool("success", true)) + attrs := []attribute.KeyValue{attribute.Bool("success", true)} if stored { - logs = append(logs, olog.Bool("stored", true)) - logs = append(logs, olog.String("reason", reason)) + attrs = append(attrs, + attribute.Bool("stored", true), + attribute.String("reason", reason), + ) } - span.LogFields(logs...) + span.SetAttributes(attrs...) } - span.Finish() + span.End() }() stamp := new(postage.Stamp) @@ -535,15 +539,17 @@ func (ps *PushSync) push(parentCtx context.Context, resultChan chan<- receiptRes now := time.Now() - spanInner, _, _ := ps.tracer.FollowSpanFromContext(context.WithoutCancel(parentCtx), "push-chunk-async", ps.logger, opentracing.Tag{Key: "address", Value: ch.Address().String()}) + spanInner, _, _ := ps.tracer.FollowSpanFromContext(context.WithoutCancel(parentCtx), "push-chunk-async", ps.logger, trace.WithAttributes( + attribute.String("address", ch.Address().String()), + )) defer func() { if err != nil { - ext.LogError(spanInner, err) + tracing.RecordError(spanInner, err) } else { - spanInner.LogFields(olog.Bool("success", true)) + spanInner.SetAttributes(attribute.Bool("success", true)) } - spanInner.Finish() + spanInner.End() select { case resultChan <- receiptResult{pushTime: now, peer: peer, err: err, receipt: receipt}: case <-parentCtx.Done(): @@ -552,9 +558,9 @@ func (ps *PushSync) push(parentCtx context.Context, resultChan chan<- receiptRes defer action.Cleanup() - spanInner.LogFields(olog.String("peer_address", peer.String())) + spanInner.SetAttributes(attribute.String("peer_address", peer.String())) - receipt, err = ps.pushChunkToPeer(tracing.WithContext(ctx, spanInner.Context()), peer, ch) + receipt, err = ps.pushChunkToPeer(tracing.WithContext(ctx, spanInner.SpanContext()), peer, ch) if err != nil { return } diff --git a/pkg/retrieval/retrieval.go b/pkg/retrieval/retrieval.go index 9432a058690..f4bc9844a33 100644 --- a/pkg/retrieval/retrieval.go +++ b/pkg/retrieval/retrieval.go @@ -27,9 +27,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/topology" "github.com/ethersphere/bee/v2/pkg/tracing" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" - olog "github.com/opentracing/opentracing-go/log" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "resenje.org/singleflight" ) @@ -259,8 +258,10 @@ func (s *Service) RetrieveChunk(ctx context.Context, chunkAddr, sourcePeerAddr s inflight++ go func() { - span, _, ctx := s.tracer.FollowSpanFromContext(spanCtx, "retrieve-chunk", s.logger, opentracing.Tag{Key: "address", Value: chunkAddr.String()}) - defer span.Finish() + span, _, ctx := s.tracer.FollowSpanFromContext(spanCtx, "retrieve-chunk", s.logger, trace.WithAttributes( + attribute.String("address", chunkAddr.String()), + )) + defer span.End() s.retrieveChunk(ctx, quit, chunkAddr, peer, resultC, action, span) }() @@ -295,7 +296,7 @@ func (s *Service) RetrieveChunk(ctx context.Context, chunkAddr, sourcePeerAddr s return v, nil } -func (s *Service) retrieveChunk(ctx context.Context, quit chan struct{}, chunkAddr, peer swarm.Address, result chan retrievalResult, action accounting.Action, span opentracing.Span) { +func (s *Service) retrieveChunk(ctx context.Context, quit chan struct{}, chunkAddr, peer swarm.Address, result chan retrievalResult, action accounting.Action, span trace.Span) { var ( startTime = time.Now() err error @@ -305,10 +306,10 @@ func (s *Service) retrieveChunk(ctx context.Context, quit chan struct{}, chunkAd defer func() { action.Cleanup() if err != nil { - ext.LogError(span, err) + tracing.RecordError(span, err) s.metrics.TotalErrors.Inc() } else { - span.LogFields(olog.Bool("success", true)) + span.SetAttributes(attribute.Bool("success", true)) } select { case result <- retrievalResult{err: err, chunk: chunk, peer: peer}: @@ -449,15 +450,17 @@ func (s *Service) handler(p2pctx context.Context, p p2p.Peer, stream p2p.Stream) var forwarded bool - span, _, ctx := s.tracer.StartSpanFromContext(ctx, "handle-retrieve-chunk", s.logger, opentracing.Tag{Key: "address", Value: addr.String()}) + span, _, ctx := s.tracer.StartSpanFromContext(ctx, "handle-retrieve-chunk", s.logger, trace.WithAttributes( + attribute.String("address", addr.String()), + )) defer func() { if err != nil { - ext.LogError(span, err) + tracing.RecordError(span, err) } else { - span.LogFields(olog.Bool("success", true)) + span.SetAttributes(attribute.Bool("success", true)) } - span.LogFields(olog.Bool("forwarded", forwarded)) - span.Finish() + span.SetAttributes(attribute.Bool("forwarded", forwarded)) + span.End() }() chunk, err := s.storer.Lookup().Get(ctx, addr) diff --git a/pkg/storer/netstore.go b/pkg/storer/netstore.go index e2becfb5027..1ec9e199fcf 100644 --- a/pkg/storer/netstore.go +++ b/pkg/storer/netstore.go @@ -13,8 +13,8 @@ import ( "github.com/ethersphere/bee/v2/pkg/storage" "github.com/ethersphere/bee/v2/pkg/swarm" "github.com/ethersphere/bee/v2/pkg/topology" - "github.com/opentracing/opentracing-go/ext" - olog "github.com/opentracing/opentracing-go/log" + "github.com/ethersphere/bee/v2/pkg/tracing" + "go.opentelemetry.io/otel/attribute" "golang.org/x/sync/errgroup" ) @@ -34,9 +34,9 @@ func (db *DB) DirectUpload() PutterSession { span, logger, ctx := db.tracer.FollowSpanFromContext(ctx, "put-direct-upload", db.logger) defer func() { if err != nil { - ext.LogError(span, err) + tracing.RecordError(span, err) } - span.Finish() + span.End() }() for { @@ -85,20 +85,20 @@ func (db *DB) Download(cache bool) storage.Getter { span, logger, ctx := db.tracer.StartSpanFromContext(ctx, "get-chunk", db.logger) defer func() { if err != nil { - ext.LogError(span, err) + tracing.RecordError(span, err) } else { - span.LogFields(olog.Bool("success", true)) + span.SetAttributes(attribute.Bool("success", true)) } - span.Finish() + span.End() }() ch, err = db.Lookup().Get(ctx, address) switch { case err == nil: - span.LogFields(olog.String("step", "chunk found locally")) + span.AddEvent("chunk found locally") return ch, nil case errors.Is(err, storage.ErrNotFound): - span.LogFields(olog.String("step", "retrieve chunk from network")) + span.AddEvent("retrieve chunk from network") if db.retrieval != nil { // if chunk is not found locally, retrieve it from the network ch, err = db.retrieval.RetrieveChunk(ctx, address, swarm.ZeroAddress) diff --git a/pkg/tracing/doc.go b/pkg/tracing/doc.go index 3f6bc7853f4..dc06e735aac 100644 --- a/pkg/tracing/doc.go +++ b/pkg/tracing/doc.go @@ -7,15 +7,14 @@ Package tracing helps with the propagation of the tracing span through context in the system. It does this for operations contained to single node, as well as across nodes, by injecting special headers. -To use the tracing package, a Tracer instance must be created, which contains -functions for starting new span contexts, injecting them in other data, and -extracting the active span them from the context. +The package wraps the OpenTelemetry Go SDK and ships spans via the OTLP/HTTP +exporter, so any OTLP-compatible backend (Jaeger v2, Tempo, Honeycomb, …) works. To use the tracing package a Tracer instance must be created: tracer, tracerCloser, err := tracing.NewTracer(&tracing.Options{ Enabled: true, - Endpoint: "127.0.0.1:6831", + Endpoint: "127.0.0.1:4318", ServiceName: "bee", }) if err != nil { @@ -25,13 +24,13 @@ To use the tracing package a Tracer instance must be created: // ... The tracer instance contains functions for starting new span contexts, injecting -them in other data, and extracting the active span them from the context: +them in other data, and extracting the active span from the context: span, _, ctx := tracer.StartSpanFromContext(ctx, "operation-name", nil) -Once the operation is finished, the open span should be finished: +Once the operation is finished, the open span should be ended: - span.Finish() + span.End() The tracing package also provides a function for creating a logger which will inject a "traceID" field entry to the log line, which helps in finding out which diff --git a/pkg/tracing/tracing.go b/pkg/tracing/tracing.go index e9c563464fa..37747d991b5 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/tracing/tracing.go @@ -2,278 +2,389 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Package tracing wraps the OpenTelemetry SDK and exposes a small set of +// helpers for starting spans, propagating span context across libp2p streams +// and HTTP requests, and annotating loggers with trace ids. package tracing import ( - "bufio" - "bytes" "context" + "crypto/tls" + "crypto/x509" "errors" + "fmt" "io" "net/http" + "os" "time" "github.com/ethersphere/bee/v2/pkg/log" "github.com/ethersphere/bee/v2/pkg/p2p" - "github.com/opentracing/opentracing-go" - "github.com/uber/jaeger-client-go" - "github.com/uber/jaeger-client-go/config" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "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.25.0" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" + "google.golang.org/grpc/credentials" ) -var ( - // ErrContextNotFound is returned when tracing context is not present - // in p2p Headers or context. - ErrContextNotFound = errors.New("tracing context not found") +// ErrContextNotFound is returned when tracing context is not present in +// p2p Headers, HTTP headers, or the go context. +var ErrContextNotFound = errors.New("tracing context not found") - // noopTracer is the tracer that does nothing to handle a nil Tracer usage. - noopTracer = &Tracer{tracer: new(opentracing.NoopTracer)} -) +// LogField is the key in log message field that holds the tracing id value. +const LogField = "traceID" -// contextKey is used to reference a tracing context span as context value. -type contextKey struct{} +// instrumentationName identifies spans produced by this package to the OTel SDK. +const instrumentationName = "github.com/ethersphere/bee/v2/pkg/tracing" -// LogField is the key in log message field that holds tracing id value. -const LogField = "traceID" +// p2pCarrierVersion is the leading byte of the libp2p binary span context +// payload. It exists so the wire format can evolve without silently breaking +// peers running an older binary. +const p2pCarrierVersion byte = 1 -const ( - // TraceContextHeaderName is the http header name used to propagate tracing context. - TraceContextHeaderName = "swarm-trace-id" +// p2pCarrierLen is the encoded payload size: +// 1 version + 16 TraceID + 8 SpanID + 1 TraceFlags. +const p2pCarrierLen = 26 - // TraceBaggageHeaderPrefix is the prefix for http headers used to propagate baggage. - TraceBaggageHeaderPrefix = "swarmctx-" -) +// shutdownTimeout bounds how long Close waits for the OTel batch processor to +// flush pending spans on shutdown. +const shutdownTimeout = 5 * time.Second + +// noopTracer is returned when tracing is disabled or *Tracer is nil so callers +// always operate against a working trace.Tracer. +var noopTracer = &Tracer{tracer: noop.NewTracerProvider().Tracer(instrumentationName)} -// Tracer connect to a tracing server and handles tracing spans and contexts -// by using opentracing Tracer. +// Tracer wraps an OTel Tracer and provides p2p/HTTP carriers plus helpers +// aligned with bee's tracing API. type Tracer struct { - tracer opentracing.Tracer + tracer trace.Tracer } -// Options are optional parameters for Tracer constructor. +// Options are the constructor parameters for Tracer. type Options struct { - Enabled bool - Endpoint string + // Enabled toggles span recording. When false the tracer is a no-op. + Enabled bool + // Endpoint is the OTLP collector endpoint, e.g. "127.0.0.1:4318" for http + // or "127.0.0.1:4317" for grpc. Required when Enabled is true. + Endpoint string + // ServiceName is reported as the OTel service.name resource attribute. ServiceName string + // Insecure disables TLS for the OTLP exporter (useful for a local collector). + Insecure bool + // CAFile is an optional path to a PEM-encoded CA bundle used to verify + // the OTLP collector certificate. Ignored when Insecure is true. When + // empty and Insecure is false, the system root CAs are used. + CAFile string + // SamplingRatio is the head-based sampling ratio for the parent-based + // sampler in the range [0, 1]. 0 disables sampling for non-parented spans; + // 1 samples everything. Negative values are clamped to 0. + SamplingRatio float64 + // Protocol selects the OTLP exporter transport: "http" or "grpc". Empty + // defaults to "http". + Protocol string + // Logger, when non-nil, receives startup warnings (e.g. missing CA bundle + // when TLS is enabled). NewTracer does not log otherwise. + Logger log.Logger } -// NewTracer creates a new Tracer and returns a closer which needs to be closed -// when the Tracer is no longer used to flush remaining traces. +// NewTracer creates a new Tracer and returns a closer that flushes pending +// spans and shuts down the OTel pipeline. func NewTracer(o *Options) (*Tracer, io.Closer, error) { if o == nil { o = new(Options) } - cfg := config.Configuration{ - Disabled: !o.Enabled, - ServiceName: o.ServiceName, - Sampler: &config.SamplerConfig{ - Type: jaeger.SamplerTypeConst, - Param: 1, - }, - Reporter: &config.ReporterConfig{ - LogSpans: true, - BufferFlushInterval: 1 * time.Second, - LocalAgentHostPort: o.Endpoint, - }, - Headers: &jaeger.HeadersConfig{ - TraceContextHeaderName: TraceContextHeaderName, - TraceBaggageHeaderPrefix: TraceBaggageHeaderPrefix, - }, + if !o.Enabled { + return noopTracer, noopCloser{}, nil } - t, closer, err := cfg.NewTracer() + if o.Endpoint == "" { + return nil, nil, errors.New("tracing-otlp-endpoint is required when tracing is enabled") + } + + if !o.Insecure && o.CAFile == "" && o.Logger != nil { + o.Logger.Warning("tracing: TLS is enabled but no CA bundle is configured; the OTLP exporter will rely on the system root CAs. Provide --tracing-otlp-ca-file, set --tracing-otlp-insecure=true for a plaintext local collector, or disable tracing.") + } + + res, err := resource.New(context.Background(), + resource.WithAttributes(semconv.ServiceName(o.ServiceName)), + ) + if err != nil { + return nil, nil, fmt.Errorf("otel resource: %w", err) + } + + ratio := o.SamplingRatio + if ratio < 0 { + ratio = 0 + } + + client, err := newOTLPClient(o) if err != nil { return nil, nil, err } - return &Tracer{tracer: t}, closer, nil + exporter, err := otlptrace.New(context.Background(), client) + if err != nil { + return nil, nil, fmt.Errorf("otlp exporter: %w", err) + } + + tp := sdktrace.NewTracerProvider( + sdktrace.WithResource(res), + sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(ratio))), + sdktrace.WithBatcher(exporter), + ) + return &Tracer{tracer: tp.Tracer(instrumentationName)}, providerCloser{tp: tp}, nil } -// StartSpanFromContext starts a new tracing span that is either a root one or a -// child of existing one from the provided Context. If logger is provided, a new -// log Entry will be returned with "traceID" log field. -func (t *Tracer) StartSpanFromContext(ctx context.Context, operationName string, l log.Logger, opts ...opentracing.StartSpanOption) (opentracing.Span, log.Logger, context.Context) { - if t == nil { - t = noopTracer +// Supported OTLP transport values for Options.Protocol. +const ( + ProtocolHTTP = "http" + ProtocolGRPC = "grpc" +) + +// newOTLPClient builds the OTLP client for the configured transport. An empty +// Protocol defaults to HTTP for backward compatibility with the initial OTLP +// rollout. +func newOTLPClient(o *Options) (otlptrace.Client, error) { + switch o.Protocol { + case "", ProtocolHTTP: + opts := []otlptracehttp.Option{otlptracehttp.WithEndpoint(o.Endpoint)} + if o.Insecure { + opts = append(opts, otlptracehttp.WithInsecure()) + } else if o.CAFile != "" { + tlsConfig, err := loadCAFile(o.CAFile) + if err != nil { + return nil, err + } + opts = append(opts, otlptracehttp.WithTLSClientConfig(tlsConfig)) + } + return otlptracehttp.NewClient(opts...), nil + case ProtocolGRPC: + opts := []otlptracegrpc.Option{otlptracegrpc.WithEndpoint(o.Endpoint)} + if o.Insecure { + opts = append(opts, otlptracegrpc.WithInsecure()) + } else if o.CAFile != "" { + tlsConfig, err := loadCAFile(o.CAFile) + if err != nil { + return nil, err + } + opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) + } + return otlptracegrpc.NewClient(opts...), nil + default: + return nil, fmt.Errorf("unsupported otlp protocol %q (want %q or %q)", o.Protocol, ProtocolHTTP, ProtocolGRPC) } +} - var span opentracing.Span - if parentContext := FromContext(ctx); parentContext != nil { - opts = append(opts, opentracing.ChildOf(parentContext)) - span = t.tracer.StartSpan(operationName, opts...) - } else { - span = t.tracer.StartSpan(operationName, opts...) +// loadCAFile reads a PEM-encoded CA bundle from path and returns a *tls.Config +// that uses it as the only root for OTLP collector certificate verification. +func loadCAFile(path string) (*tls.Config, error) { + pem, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read tracing CA file %q: %w", path, err) } - sc := span.Context() - return span, loggerWithTraceID(sc, l), WithContext(ctx, sc) + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(pem) { + return nil, fmt.Errorf("tracing CA file %q contains no valid PEM certificates", path) + } + return &tls.Config{RootCAs: pool, MinVersion: tls.VersionTLS12}, nil } -// FollowSpanFromContext starts a new tracing span that is either a root one or -// follows an existing one from the provided Context. If logger is provided, a new -// log Entry will be returned with "traceID" log field. -func (t *Tracer) FollowSpanFromContext(ctx context.Context, operationName string, l log.Logger, opts ...opentracing.StartSpanOption) (opentracing.Span, log.Logger, context.Context) { +// StartSpanFromContext starts a new span as a child of any span context already +// present in ctx. If logger is non-nil, a derived logger annotated with the +// trace id is returned alongside the new context. +func (t *Tracer) StartSpanFromContext(ctx context.Context, operationName string, l log.Logger, opts ...trace.SpanStartOption) (trace.Span, log.Logger, context.Context) { if t == nil { t = noopTracer } - var span opentracing.Span - if parentContext := FromContext(ctx); parentContext != nil { - opts = append(opts, opentracing.FollowsFrom(parentContext)) - span = t.tracer.StartSpan(operationName, opts...) - } else { - span = t.tracer.StartSpan(operationName, opts...) + if parent := FromContext(ctx); parent.IsValid() { + ctx = trace.ContextWithSpanContext(ctx, parent) } - sc := span.Context() - return span, loggerWithTraceID(sc, l), WithContext(ctx, sc) + + ctx, span := t.tracer.Start(ctx, operationName, opts...) + return span, loggerWithTraceID(span.SpanContext(), l), ctx } -// AddContextHeader adds a tracing span context to provided p2p Headers from -// the go context. If the tracing span context is not present in go context, -// ErrContextNotFound is returned. -func (t *Tracer) AddContextHeader(ctx context.Context, headers p2p.Headers) error { +// FollowSpanFromContext starts a new span with a Link to the span context in +// ctx. Links are the OTel equivalent of OpenTracing's FollowsFrom relation: +// the new span is causally related but not a direct child. +func (t *Tracer) FollowSpanFromContext(ctx context.Context, operationName string, l log.Logger, opts ...trace.SpanStartOption) (trace.Span, log.Logger, context.Context) { if t == nil { t = noopTracer } - c := FromContext(ctx) - if c == nil { - return ErrContextNotFound + if parent := FromContext(ctx); parent.IsValid() { + opts = append(opts, trace.WithLinks(trace.Link{SpanContext: parent})) } - var b bytes.Buffer - w := bufio.NewWriter(&b) - if err := t.tracer.Inject(c, opentracing.Binary, w); err != nil { - return err - } - if err := w.Flush(); err != nil { - return err - } + ctx, span := t.tracer.Start(ctx, operationName, opts...) + return span, loggerWithTraceID(span.SpanContext(), l), ctx +} - headers[p2p.HeaderNameTracingSpanContext] = b.Bytes() +// AddContextHeader serialises the active span context into the bee p2p header. +// It is safe to call on a nil receiver. +func (t *Tracer) AddContextHeader(ctx context.Context, headers p2p.Headers) error { + sc := FromContext(ctx) + if !sc.IsValid() { + return ErrContextNotFound + } + headers[p2p.HeaderNameTracingSpanContext] = encodeP2PSpanContext(sc) return nil } -// FromHeaders returns tracing span context from p2p Headers. If the tracing -// span context is not present in go context, ErrContextNotFound is returned. -func (t *Tracer) FromHeaders(headers p2p.Headers) (opentracing.SpanContext, error) { - if t == nil { - t = noopTracer - } - +// FromHeaders extracts the span context from the bee p2p header. ErrContextNotFound +// is returned when the header is absent or when its payload is undecodable — +// the latter lets mixed-version peers degrade to per-hop trace continuity loss +// rather than failing the stream. Safe to call on a nil receiver. +func (t *Tracer) FromHeaders(headers p2p.Headers) (trace.SpanContext, error) { v := headers[p2p.HeaderNameTracingSpanContext] if v == nil { - return nil, ErrContextNotFound + return trace.SpanContext{}, ErrContextNotFound } - c, err := t.tracer.Extract(opentracing.Binary, bytes.NewReader(v)) - if err != nil { - if errors.Is(err, opentracing.ErrSpanContextNotFound) { - return nil, ErrContextNotFound - } - return nil, err + sc, ok := decodeP2PSpanContext(v) + if !ok { + return trace.SpanContext{}, ErrContextNotFound } - - return c, nil + return sc, nil } -// WithContextFromHeaders returns a new context with injected tracing span -// context if they are found in p2p Headers. If the tracing span context is not -// present in go context, ErrContextNotFound is returned. +// WithContextFromHeaders extracts a span context from the p2p header and +// returns a new context carrying it. Safe to call on a nil receiver. func (t *Tracer) WithContextFromHeaders(ctx context.Context, headers p2p.Headers) (context.Context, error) { - if t == nil { - t = noopTracer - } - - c, err := t.FromHeaders(headers) + sc, err := t.FromHeaders(headers) if err != nil { return ctx, err } - return WithContext(ctx, c), nil + return WithContext(ctx, sc), nil } -// AddContextHTTPHeader adds a tracing span context to provided HTTP headers -// from the go context. If the tracing span context is not present in -// go context, ErrContextNotFound is returned. -func (t *Tracer) AddContextHTTPHeader(ctx context.Context, headers http.Header) error { - if t == nil { - t = noopTracer - } +// httpPropagator carries trace context across HTTP via the W3C TraceContext +// standard headers (traceparent, tracestate). +var httpPropagator propagation.TextMapPropagator = propagation.TraceContext{} - c := FromContext(ctx) - if c == nil { +// AddContextHTTPHeader injects the active span context into HTTP headers. +// Safe to call on a nil receiver. +func (t *Tracer) AddContextHTTPHeader(ctx context.Context, headers http.Header) error { + sc := FromContext(ctx) + if !sc.IsValid() { return ErrContextNotFound } - carrier := opentracing.HTTPHeadersCarrier(headers) - return t.tracer.Inject(c, opentracing.HTTPHeaders, carrier) + httpPropagator.Inject(trace.ContextWithSpanContext(ctx, sc), propagation.HeaderCarrier(headers)) + return nil } -// FromHTTPHeaders returns tracing span context from HTTP headers. If the tracing -// span context is not present in go context, ErrContextNotFound is returned. -func (t *Tracer) FromHTTPHeaders(headers http.Header) (opentracing.SpanContext, error) { - if t == nil { - t = noopTracer - } - - carrier := opentracing.HTTPHeadersCarrier(headers) - c, err := t.tracer.Extract(opentracing.HTTPHeaders, carrier) - if err != nil { - if errors.Is(err, opentracing.ErrSpanContextNotFound) { - return nil, ErrContextNotFound - } - return nil, err +// FromHTTPHeaders extracts a span context from a W3C traceparent header. +// Safe to call on a nil receiver. +func (t *Tracer) FromHTTPHeaders(headers http.Header) (trace.SpanContext, error) { + ctx := httpPropagator.Extract(context.Background(), propagation.HeaderCarrier(headers)) + sc := trace.SpanContextFromContext(ctx) + if !sc.IsValid() { + return trace.SpanContext{}, ErrContextNotFound } - - return c, nil + return sc, nil } -// WithContextFromHTTPHeaders returns a new context with injected tracing span -// context if they are found in HTTP headers. If the tracing span context is not -// present in go context, ErrContextNotFound is returned. +// WithContextFromHTTPHeaders extracts a span context from HTTP headers and +// returns a new context carrying it. Safe to call on a nil receiver. func (t *Tracer) WithContextFromHTTPHeaders(ctx context.Context, headers http.Header) (context.Context, error) { - if t == nil { - t = noopTracer - } - - c, err := t.FromHTTPHeaders(headers) + sc, err := t.FromHTTPHeaders(headers) if err != nil { return ctx, err } + return WithContext(ctx, sc), nil +} - return WithContext(ctx, c), nil +// WithContext stores a span context in ctx using the standard OTel context +// key, so any OTel-aware code (propagators, exporters) can find it. +func WithContext(ctx context.Context, sc trace.SpanContext) context.Context { + return trace.ContextWithSpanContext(ctx, sc) } -// WithContext adds tracing span context to go context. -func WithContext(ctx context.Context, c opentracing.SpanContext) context.Context { - return context.WithValue(ctx, contextKey{}, c) +// FromContext returns the span context currently associated with ctx. The +// returned SpanContext's IsValid() reports false when none is present. +func FromContext(ctx context.Context) trace.SpanContext { + return trace.SpanContextFromContext(ctx) } -// FromContext return tracing span context from go context. If the tracing span -// context is not present in go context, nil is returned. -func FromContext(ctx context.Context) opentracing.SpanContext { - c, ok := ctx.Value(contextKey{}).(opentracing.SpanContext) - if !ok { - return nil - } - return c +// RecordError attaches an error event to the span, marks the span status as +// Error, and records the supplied attributes alongside the error event. It is +// the OTel equivalent of the OpenTracing ext.LogError pattern bee used previously. +func RecordError(span trace.Span, err error, attrs ...attribute.KeyValue) { + span.RecordError(err, trace.WithAttributes(attrs...)) + span.SetStatus(codes.Error, err.Error()) } -// NewLoggerWithTraceID creates a new log Entry with "traceID" field added if it -// exists in tracing span context stored from go context. +// NewLoggerWithTraceID returns a logger annotated with the trace id from ctx, +// or the original logger if no valid span context is present. func NewLoggerWithTraceID(ctx context.Context, l log.Logger) log.Logger { return loggerWithTraceID(FromContext(ctx), l) } -func loggerWithTraceID(sc opentracing.SpanContext, l log.Logger) log.Logger { +func loggerWithTraceID(sc trace.SpanContext, l log.Logger) log.Logger { if l == nil { return nil } - jsc, ok := sc.(jaeger.SpanContext) - if !ok { + if !sc.HasTraceID() { return l } - traceID := jsc.TraceID() - if !traceID.IsValid() { - return l + return l.WithValues(LogField, sc.TraceID().String()).Build() +} + +// encodeP2PSpanContext writes the trace+span ids and flags into a fixed-width +// payload. TraceState is omitted intentionally — bee does not use vendor +// tracestate routing. +func encodeP2PSpanContext(sc trace.SpanContext) []byte { + buf := make([]byte, p2pCarrierLen) + buf[0] = p2pCarrierVersion + tid := sc.TraceID() + sid := sc.SpanID() + copy(buf[1:17], tid[:]) + copy(buf[17:25], sid[:]) + buf[25] = byte(sc.TraceFlags()) + return buf +} + +func decodeP2PSpanContext(b []byte) (trace.SpanContext, bool) { + if len(b) != p2pCarrierLen || b[0] != p2pCarrierVersion { + return trace.SpanContext{}, false } - return l.WithValues(LogField, traceID).Build() + var tid trace.TraceID + var sid trace.SpanID + copy(tid[:], b[1:17]) + copy(sid[:], b[17:25]) + sc := trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: tid, + SpanID: sid, + TraceFlags: trace.TraceFlags(b[25]), + Remote: true, + }) + if !sc.IsValid() { + return trace.SpanContext{}, false + } + return sc, true +} + +// noopCloser is returned when tracing is disabled. +type noopCloser struct{} + +func (noopCloser) Close() error { return nil } + +// providerCloser flushes pending spans and stops the batch processor on Close. +type providerCloser struct { + tp *sdktrace.TracerProvider +} + +func (c providerCloser) Close() error { + ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) + defer cancel() + return c.tp.Shutdown(ctx) } diff --git a/pkg/tracing/tracing_test.go b/pkg/tracing/tracing_test.go index c6acb330b18..b2842eea980 100644 --- a/pkg/tracing/tracing_test.go +++ b/pkg/tracing/tracing_test.go @@ -8,14 +8,13 @@ import ( "bytes" "context" "encoding/json" - "fmt" + "errors" "testing" "github.com/ethersphere/bee/v2/pkg/log" "github.com/ethersphere/bee/v2/pkg/p2p" "github.com/ethersphere/bee/v2/pkg/tracing" "github.com/ethersphere/bee/v2/pkg/util/testutil" - "github.com/uber/jaeger-client-go" ) func TestSpanFromHeaders(t *testing.T) { @@ -24,7 +23,7 @@ func TestSpanFromHeaders(t *testing.T) { tracer := newTracer(t) span, _, ctx := tracer.StartSpanFromContext(context.Background(), "some-operation", nil) - defer span.Finish() + defer span.End() headers := make(p2p.Headers) if err := tracer.AddContextHeader(ctx, headers); err != nil { @@ -36,17 +35,20 @@ func TestSpanFromHeaders(t *testing.T) { t.Fatal(err) } - if fmt.Sprint(gotSpanContext) == "" { - t.Fatal("got empty span context") + if !gotSpanContext.IsValid() { + t.Fatal("got invalid span context") } - wantSpanContext := span.Context() - if fmt.Sprint(wantSpanContext) == "" { - t.Fatal("got empty start span context") + wantSpanContext := span.SpanContext() + if !wantSpanContext.IsValid() { + t.Fatal("got invalid start span context") } - if fmt.Sprint(gotSpanContext) != fmt.Sprint(wantSpanContext) { - t.Errorf("got span context %+v, want %+v", gotSpanContext, wantSpanContext) + if gotSpanContext.TraceID() != wantSpanContext.TraceID() { + t.Errorf("got trace id %s, want %s", gotSpanContext.TraceID(), wantSpanContext.TraceID()) + } + if gotSpanContext.SpanID() != wantSpanContext.SpanID() { + t.Errorf("got span id %s, want %s", gotSpanContext.SpanID(), wantSpanContext.SpanID()) } } @@ -56,7 +58,7 @@ func TestSpanWithContextFromHeaders(t *testing.T) { tracer := newTracer(t) span, _, ctx := tracer.StartSpanFromContext(context.Background(), "some-operation", nil) - defer span.Finish() + defer span.End() headers := make(p2p.Headers) if err := tracer.AddContextHeader(ctx, headers); err != nil { @@ -69,17 +71,32 @@ func TestSpanWithContextFromHeaders(t *testing.T) { } gotSpanContext := tracing.FromContext(ctx) - if fmt.Sprint(gotSpanContext) == "" { - t.Fatal("got empty span context") + if !gotSpanContext.IsValid() { + t.Fatal("got invalid span context") } - wantSpanContext := span.Context() - if fmt.Sprint(wantSpanContext) == "" { - t.Fatal("got empty start span context") + wantSpanContext := span.SpanContext() + if gotSpanContext.TraceID() != wantSpanContext.TraceID() { + t.Errorf("got trace id %s, want %s", gotSpanContext.TraceID(), wantSpanContext.TraceID()) } +} + +// TestFromHeaders_undecodablePayload exercises the rolling-upgrade tolerance: +// peers running an older binary may send a payload in a different format and we +// must not fail the stream, just return ErrContextNotFound so trace continuity +// silently breaks at that hop. +func TestFromHeaders_undecodablePayload(t *testing.T) { + t.Parallel() + + tracer := newTracer(t) - if fmt.Sprint(gotSpanContext) != fmt.Sprint(wantSpanContext) { - t.Errorf("got span context %+v, want %+v", gotSpanContext, wantSpanContext) + headers := p2p.Headers{ + p2p.HeaderNameTracingSpanContext: []byte("not-a-valid-binary-carrier"), + } + + _, err := tracer.FromHeaders(headers) + if !errors.Is(err, tracing.ErrContextNotFound) { + t.Errorf("got error %v, want %v", err, tracing.ErrContextNotFound) } } @@ -89,20 +106,20 @@ func TestFromContext(t *testing.T) { tracer := newTracer(t) span, _, ctx := tracer.StartSpanFromContext(context.Background(), "some-operation", nil) - defer span.Finish() + defer span.End() - wantSpanContext := span.Context() - if fmt.Sprint(wantSpanContext) == "" { - t.Fatal("got empty start span context") + wantSpanContext := span.SpanContext() + if !wantSpanContext.IsValid() { + t.Fatal("got invalid start span context") } gotSpanContext := tracing.FromContext(ctx) - if fmt.Sprint(gotSpanContext) == "" { - t.Fatal("got empty span context") + if !gotSpanContext.IsValid() { + t.Fatal("got invalid span context") } - if fmt.Sprint(gotSpanContext) != fmt.Sprint(wantSpanContext) { - t.Errorf("got span context %+v, want %+v", gotSpanContext, wantSpanContext) + if gotSpanContext.TraceID() != wantSpanContext.TraceID() { + t.Errorf("got trace id %s, want %s", gotSpanContext.TraceID(), wantSpanContext.TraceID()) } } @@ -112,22 +129,17 @@ func TestWithContext(t *testing.T) { tracer := newTracer(t) span, _, _ := tracer.StartSpanFromContext(context.Background(), "some-operation", nil) - defer span.Finish() + defer span.End() - wantSpanContext := span.Context() - if fmt.Sprint(wantSpanContext) == "" { - t.Fatal("got empty start span context") + wantSpanContext := span.SpanContext() + if !wantSpanContext.IsValid() { + t.Fatal("got invalid start span context") } - ctx := tracing.WithContext(context.Background(), span.Context()) - + ctx := tracing.WithContext(context.Background(), wantSpanContext) gotSpanContext := tracing.FromContext(ctx) - if fmt.Sprint(gotSpanContext) == "" { - t.Fatal("got empty span context") - } - - if fmt.Sprint(gotSpanContext) != fmt.Sprint(wantSpanContext) { - t.Errorf("got span context %+v, want %+v", gotSpanContext, wantSpanContext) + if gotSpanContext.TraceID() != wantSpanContext.TraceID() { + t.Errorf("got trace id %s, want %s", gotSpanContext.TraceID(), wantSpanContext.TraceID()) } } @@ -139,9 +151,9 @@ func TestStartSpanFromContext_logger(t *testing.T) { buf := new(bytes.Buffer) span, logger, _ := tracer.StartSpanFromContext(context.Background(), "some-operation", log.NewLogger("test", log.WithSink(buf), log.WithJSONOutput())) - defer span.Finish() + defer span.End() - wantTraceID := span.Context().(jaeger.SpanContext).TraceID() + wantTraceID := span.SpanContext().TraceID().String() logger.Info("msg") data := make(map[string]any) @@ -159,8 +171,8 @@ func TestStartSpanFromContext_logger(t *testing.T) { t.Fatalf("log field %q is not string", tracing.LogField) } - if gotTraceID != wantTraceID.String() { - t.Errorf("got trace id %q, want %q", gotTraceID, wantTraceID.String()) + if gotTraceID != wantTraceID { + t.Errorf("got trace id %q, want %q", gotTraceID, wantTraceID) } } @@ -170,7 +182,7 @@ func TestStartSpanFromContext_nilLogger(t *testing.T) { tracer := newTracer(t) span, logger, _ := tracer.StartSpanFromContext(context.Background(), "some-operation", nil) - defer span.Finish() + defer span.End() if logger != nil { t.Error("logger is not nil") @@ -183,13 +195,13 @@ func TestNewLoggerWithTraceID(t *testing.T) { tracer := newTracer(t) span, _, ctx := tracer.StartSpanFromContext(context.Background(), "some-operation", nil) - defer span.Finish() + defer span.End() buf := new(bytes.Buffer) logger := tracing.NewLoggerWithTraceID(ctx, log.NewLogger("test", log.WithSink(buf), log.WithJSONOutput())) - wantTraceID := span.Context().(jaeger.SpanContext).TraceID() + wantTraceID := span.SpanContext().TraceID().String() logger.Info("msg") data := make(map[string]any) @@ -207,8 +219,8 @@ func TestNewLoggerWithTraceID(t *testing.T) { t.Fatalf("log field %q is not string", tracing.LogField) } - if gotTraceID != wantTraceID.String() { - t.Errorf("got trace id %q, want %q", gotTraceID, wantTraceID.String()) + if gotTraceID != wantTraceID { + t.Errorf("got trace id %q, want %q", gotTraceID, wantTraceID) } } @@ -218,7 +230,7 @@ func TestNewLoggerWithTraceID_nilLogger(t *testing.T) { tracer := newTracer(t) span, _, ctx := tracer.StartSpanFromContext(context.Background(), "some-operation", nil) - defer span.Finish() + defer span.End() logger := tracing.NewLoggerWithTraceID(ctx, nil) @@ -227,11 +239,17 @@ func TestNewLoggerWithTraceID_nilLogger(t *testing.T) { } } +// newTracer returns a Tracer wired to a real OTel SDK with an OTLP exporter +// pointed at a dead local endpoint. Spans receive valid contexts; the batch +// processor's failed exports are silently dropped in the background, which is +// fine for unit tests that only verify context propagation. func newTracer(t *testing.T) *tracing.Tracer { t.Helper() tracer, closer, err := tracing.NewTracer(&tracing.Options{ Enabled: true, + Endpoint: "127.0.0.1:1", + Insecure: true, ServiceName: "test", }) if err != nil {