mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-22 20:56:42 +00:00
rpc: add generic OpenTelemetry tracing for JSON-RPC calls
This commit is contained in:
parent
b635e0632c
commit
1eb79f0e16
9 changed files with 351 additions and 27 deletions
|
|
@ -34,7 +34,7 @@ require (
|
|||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -96,12 +96,12 @@ github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJf
|
|||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4=
|
||||
github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
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/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU=
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw=
|
||||
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
|
|
@ -118,8 +118,8 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.1.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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
|
|
|
|||
18
go.mod
18
go.mod
|
|
@ -31,7 +31,7 @@ require (
|
|||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/golang/snappy v1.0.0
|
||||
github.com/google/gofuzz v1.2.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/graph-gophers/graphql-go v1.3.0
|
||||
github.com/hashicorp/go-bexpr v0.1.10
|
||||
|
|
@ -56,16 +56,19 @@ require (
|
|||
github.com/rs/cors v1.7.0
|
||||
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible
|
||||
github.com/status-im/keycard-go v0.2.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
|
||||
github.com/urfave/cli/v2 v2.27.5
|
||||
go.opentelemetry.io/otel v1.39.0
|
||||
go.opentelemetry.io/otel/sdk v1.39.0
|
||||
go.opentelemetry.io/otel/trace v1.39.0
|
||||
go.uber.org/automaxprocs v1.5.2
|
||||
go.uber.org/goleak v1.3.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df
|
||||
golang.org/x/sync v0.12.0
|
||||
golang.org/x/sys v0.36.0
|
||||
golang.org/x/sys v0.39.0
|
||||
golang.org/x/text v0.23.0
|
||||
golang.org/x/time v0.9.0
|
||||
golang.org/x/tools v0.29.0
|
||||
|
|
@ -74,6 +77,13 @@ require (
|
|||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
|
|
@ -136,7 +146,7 @@ require (
|
|||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
|
|
|
|||
37
go.sum
37
go.sum
|
|
@ -136,6 +136,11 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
|
|||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
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.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
|
|
@ -170,16 +175,16 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U=
|
||||
github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
|
|
@ -322,8 +327,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
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/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
|
|
@ -343,8 +348,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw=
|
||||
github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
|
||||
|
|
@ -363,6 +368,18 @@ github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBi
|
|||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
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.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
|
|
@ -444,8 +461,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||
golang.org/x/sys v0.7.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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -88,6 +90,7 @@ type Client struct {
|
|||
// config fields
|
||||
batchItemLimit int
|
||||
batchResponseMaxSize int
|
||||
tracer trace.Tracer
|
||||
|
||||
// writeConn is used for writing to the connection on the caller's goroutine. It should
|
||||
// only be accessed outside of dispatch, with the write lock held. The write lock is
|
||||
|
|
@ -119,7 +122,7 @@ func (c *Client) newClientConn(conn ServerCodec) *clientConn {
|
|||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, clientContextKey{}, c)
|
||||
ctx = context.WithValue(ctx, peerInfoContextKey{}, conn.peerInfo())
|
||||
handler := newHandler(ctx, conn, c.idgen, c.services, c.batchItemLimit, c.batchResponseMaxSize)
|
||||
handler := newHandler(ctx, conn, c.idgen, c.services, c.batchItemLimit, c.batchResponseMaxSize, c.tracer)
|
||||
return &clientConn{conn, handler}
|
||||
}
|
||||
|
||||
|
|
@ -247,6 +250,7 @@ func initClient(conn ServerCodec, services *serviceRegistry, cfg *clientConfig)
|
|||
idgen: cfg.idgen,
|
||||
batchItemLimit: cfg.batchItemLimit,
|
||||
batchResponseMaxSize: cfg.batchResponseLimit,
|
||||
tracer: cfg.tracer,
|
||||
writeConn: conn,
|
||||
close: make(chan struct{}),
|
||||
closing: make(chan struct{}),
|
||||
|
|
@ -263,6 +267,9 @@ func initClient(conn ServerCodec, services *serviceRegistry, cfg *clientConfig)
|
|||
if c.idgen == nil {
|
||||
c.idgen = randomIDGenerator()
|
||||
}
|
||||
if c.tracer == nil {
|
||||
c.tracer = noop.NewTracerProvider().Tracer(tracerName)
|
||||
}
|
||||
|
||||
// Launch the main loop.
|
||||
if !isHTTP {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// ClientOption is a configuration option for the RPC client.
|
||||
|
|
@ -41,6 +42,9 @@ type clientConfig struct {
|
|||
idgen func() ID
|
||||
batchItemLimit int
|
||||
batchResponseLimit int
|
||||
|
||||
// Tracing
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
func (cfg *clientConfig) initHeaders() {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
|
|
@ -65,6 +70,7 @@ type handler struct {
|
|||
allowSubscribe bool
|
||||
batchRequestLimit int
|
||||
batchResponseMaxSize int
|
||||
tracer trace.Tracer // for RPC call tracing
|
||||
|
||||
subLock sync.Mutex
|
||||
serverSubs map[ID]*Subscription
|
||||
|
|
@ -73,9 +79,10 @@ type handler struct {
|
|||
type callProc struct {
|
||||
ctx context.Context
|
||||
notifiers []*Notifier
|
||||
isBatch bool // true if this call is part of a batch request
|
||||
}
|
||||
|
||||
func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry, batchRequestLimit, batchResponseMaxSize int) *handler {
|
||||
func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry, batchRequestLimit, batchResponseMaxSize int, tracer trace.Tracer) *handler {
|
||||
rootCtx, cancelRoot := context.WithCancel(connCtx)
|
||||
h := &handler{
|
||||
reg: reg,
|
||||
|
|
@ -90,6 +97,7 @@ func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *
|
|||
log: log.Root(),
|
||||
batchRequestLimit: batchRequestLimit,
|
||||
batchResponseMaxSize: batchResponseMaxSize,
|
||||
tracer: tracer,
|
||||
}
|
||||
if conn.remoteAddr() != "" {
|
||||
h.log = h.log.New("conn", conn.remoteAddr())
|
||||
|
|
@ -197,6 +205,7 @@ func (h *handler) handleBatch(msgs []*jsonrpcMessage) {
|
|||
|
||||
// Process calls on a goroutine because they may block indefinitely:
|
||||
h.startCallProc(func(cp *callProc) {
|
||||
cp.isBatch = true
|
||||
var (
|
||||
timer *time.Timer
|
||||
cancel context.CancelFunc
|
||||
|
|
@ -515,8 +524,20 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage
|
|||
if err != nil {
|
||||
return msg.errorResponse(&invalidParamsError{err.Error()})
|
||||
}
|
||||
|
||||
// Start tracing span before invoking the method.
|
||||
ctx, span := h.startRPCSpan(cp.ctx, msg, cp.isBatch)
|
||||
defer span.End()
|
||||
|
||||
start := time.Now()
|
||||
answer := h.runMethod(cp.ctx, msg, callb, args)
|
||||
answer := h.runMethod(ctx, msg, callb, args)
|
||||
|
||||
// Record error on span if method failed.
|
||||
if answer.Error != nil {
|
||||
err := errors.New(answer.Error.Message)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
}
|
||||
|
||||
// Collect the statistics for RPC calls if metrics is enabled.
|
||||
// We only care about pure rpc call. Filter out subscription.
|
||||
|
|
@ -564,12 +585,41 @@ func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMes
|
|||
}
|
||||
args = args[1:]
|
||||
|
||||
// Start tracing span before invoking the subscription method.
|
||||
ctx, span := h.startRPCSpan(cp.ctx, msg, cp.isBatch)
|
||||
defer span.End()
|
||||
|
||||
// Install notifier in context so the subscription handler can find it.
|
||||
n := &Notifier{h: h, namespace: namespace}
|
||||
cp.notifiers = append(cp.notifiers, n)
|
||||
ctx := context.WithValue(cp.ctx, notifierKey{}, n)
|
||||
ctx = context.WithValue(ctx, notifierKey{}, n)
|
||||
|
||||
return h.runMethod(ctx, msg, callb, args)
|
||||
answer := h.runMethod(ctx, msg, callb, args)
|
||||
|
||||
// Record error on span if method failed.
|
||||
if answer.Error != nil {
|
||||
err := errors.New(answer.Error.Message)
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
}
|
||||
|
||||
return answer
|
||||
}
|
||||
|
||||
// startRPCSpan starts a tracing span for an RPC call.
|
||||
func (h *handler) startRPCSpan(ctx context.Context, msg *jsonrpcMessage, isBatch bool) (context.Context, trace.Span) {
|
||||
ctx, span := h.tracer.Start(ctx, "rpc.call")
|
||||
span.SetAttributes(
|
||||
semconv.RPCSystemKey.String("jsonrpc"),
|
||||
semconv.RPCMethodKey.String(msg.Method),
|
||||
attribute.Bool("rpc.batch", isBatch),
|
||||
)
|
||||
// Request id
|
||||
id := strings.TrimSpace(string(msg.ID))
|
||||
if id != "" && id != "null" {
|
||||
span.SetAttributes(attribute.String("rpc.id", id))
|
||||
}
|
||||
return ctx, span
|
||||
}
|
||||
|
||||
// runMethod runs the Go callback for an RPC method.
|
||||
|
|
|
|||
|
|
@ -24,11 +24,15 @@ import (
|
|||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const MetadataApi = "rpc"
|
||||
const EngineApi = "engine"
|
||||
const tracerName = "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
// CodecOption specifies which type of messages a codec supports.
|
||||
//
|
||||
|
|
@ -47,6 +51,7 @@ const (
|
|||
type Server struct {
|
||||
services serviceRegistry
|
||||
idgen func() ID
|
||||
tracer trace.Tracer
|
||||
|
||||
mutex sync.Mutex
|
||||
codecs map[ServerCodec]struct{}
|
||||
|
|
@ -64,6 +69,7 @@ func NewServer() *Server {
|
|||
codecs: make(map[ServerCodec]struct{}),
|
||||
httpBodyLimit: defaultBodyLimit,
|
||||
wsReadLimit: wsDefaultReadLimit,
|
||||
tracer: noop.NewTracerProvider().Tracer(tracerName),
|
||||
}
|
||||
server.run.Store(true)
|
||||
// Register the default service providing meta information about the RPC service such
|
||||
|
|
@ -98,6 +104,15 @@ func (s *Server) SetWebsocketReadLimit(limit int64) {
|
|||
s.wsReadLimit = limit
|
||||
}
|
||||
|
||||
// SetTracerProvider configures the OpenTelemetry TracerProvider for RPC call tracing.
|
||||
func (s *Server) SetTracerProvider(tp trace.TracerProvider) {
|
||||
if tp == nil {
|
||||
s.tracer = noop.NewTracerProvider().Tracer(tracerName)
|
||||
return
|
||||
}
|
||||
s.tracer = tp.Tracer(tracerName)
|
||||
}
|
||||
|
||||
// RegisterName creates a service for the given receiver type under the given name. When no
|
||||
// methods on the given receiver match the criteria to be either an RPC method or a
|
||||
// subscription an error is returned. Otherwise a new service is created and added to the
|
||||
|
|
@ -123,6 +138,7 @@ func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) {
|
|||
idgen: s.idgen,
|
||||
batchItemLimit: s.batchItemLimit,
|
||||
batchResponseLimit: s.batchResponseLimit,
|
||||
tracer: s.tracer,
|
||||
}
|
||||
c := initClient(codec, &s.services, cfg)
|
||||
<-codec.closed()
|
||||
|
|
@ -156,7 +172,7 @@ func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) {
|
|||
return
|
||||
}
|
||||
|
||||
h := newHandler(ctx, codec, s.idgen, &s.services, s.batchItemLimit, s.batchResponseLimit)
|
||||
h := newHandler(ctx, codec, s.idgen, &s.services, s.batchItemLimit, s.batchResponseLimit, s.tracer)
|
||||
h.allowSubscribe = false
|
||||
defer h.close(io.EOF, nil)
|
||||
|
||||
|
|
|
|||
220
rpc/tracing_test.go
Normal file
220
rpc/tracing_test.go
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"go.opentelemetry.io/otel/sdk/trace/tracetest"
|
||||
)
|
||||
|
||||
func spanNames(spans []tracetest.SpanStub) []string {
|
||||
names := make([]string, len(spans))
|
||||
for i, s := range spans {
|
||||
names[i] = s.Name
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func attributeMap(attrs []attribute.KeyValue) map[string]string {
|
||||
m := make(map[string]string)
|
||||
for _, a := range attrs {
|
||||
switch a.Value.Type() {
|
||||
case attribute.STRING:
|
||||
m[string(a.Key)] = a.Value.AsString()
|
||||
case attribute.BOOL:
|
||||
if a.Value.AsBool() {
|
||||
m[string(a.Key)] = "true"
|
||||
} else {
|
||||
m[string(a.Key)] = "false"
|
||||
}
|
||||
default:
|
||||
m[string(a.Key)] = a.Value.Emit()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func newTracingServer(t *testing.T) (*Server, *sdktrace.TracerProvider, *tracetest.InMemoryExporter) {
|
||||
t.Helper()
|
||||
|
||||
exporter := tracetest.NewInMemoryExporter()
|
||||
tracer := sdktrace.NewTracerProvider(sdktrace.WithSyncer(exporter))
|
||||
t.Cleanup(func() { _ = tracer.Shutdown(context.Background()) })
|
||||
|
||||
server := newTestServer()
|
||||
server.SetTracerProvider(tracer)
|
||||
t.Cleanup(server.Stop)
|
||||
|
||||
return server, tracer, exporter
|
||||
}
|
||||
|
||||
// TestTracingHTTP verifies that RPC spans are emitted when processing HTTP requests.
|
||||
func TestTracingHTTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
server, tracer, exporter := newTracingServer(t)
|
||||
|
||||
httpsrv := httptest.NewServer(server)
|
||||
t.Cleanup(httpsrv.Close)
|
||||
|
||||
client, err := DialHTTP(httpsrv.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
t.Cleanup(client.Close)
|
||||
|
||||
var result echoResult
|
||||
if err := client.Call(&result, "test_echo", "hello", 42, &echoArgs{S: "world"}); err != nil {
|
||||
t.Fatalf("RPC call failed: %v", err)
|
||||
}
|
||||
|
||||
if err := tracer.ForceFlush(context.Background()); err != nil {
|
||||
t.Fatalf("failed to flush: %v", err)
|
||||
}
|
||||
|
||||
spans := exporter.GetSpans()
|
||||
if len(spans) == 0 {
|
||||
t.Fatal("no spans were emitted")
|
||||
}
|
||||
|
||||
var rpcSpan *tracetest.SpanStub
|
||||
for i := range spans {
|
||||
if spans[i].Name == "rpc.call" {
|
||||
rpcSpan = &spans[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if rpcSpan == nil {
|
||||
t.Fatalf("rpc.call span not found, got %v", spanNames(spans))
|
||||
}
|
||||
|
||||
attrs := attributeMap(rpcSpan.Attributes)
|
||||
if attrs["rpc.system"] != "jsonrpc" {
|
||||
t.Errorf("expected rpc.system=jsonrpc, got %v", attrs["rpc.system"])
|
||||
}
|
||||
if attrs["rpc.method"] != "test_echo" {
|
||||
t.Errorf("expected rpc.method=test_echo, got %v", attrs["rpc.method"])
|
||||
}
|
||||
if _, ok := attrs["rpc.id"]; !ok {
|
||||
t.Errorf("expected rpc.id attribute to be set")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTracingInProcess verifies that RPC spans are emitted when processing requests in process.
|
||||
func TestTracingInProcess(t *testing.T) {
|
||||
t.Parallel()
|
||||
server, tracer, exporter := newTracingServer(t)
|
||||
|
||||
client := DialInProc(server)
|
||||
t.Cleanup(client.Close)
|
||||
|
||||
var result echoResult
|
||||
if err := client.Call(&result, "test_echo", "hello", 42, &echoArgs{S: "world"}); err != nil {
|
||||
t.Fatalf("RPC call failed: %v", err)
|
||||
}
|
||||
|
||||
if err := tracer.ForceFlush(context.Background()); err != nil {
|
||||
t.Fatalf("failed to flush: %v", err)
|
||||
}
|
||||
|
||||
spans := exporter.GetSpans()
|
||||
if len(spans) == 0 {
|
||||
t.Fatal("no spans were emitted")
|
||||
}
|
||||
|
||||
var rpcSpan *tracetest.SpanStub
|
||||
for i := range spans {
|
||||
if spans[i].Name == "rpc.call" {
|
||||
rpcSpan = &spans[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if rpcSpan == nil {
|
||||
t.Fatalf("rpc.call span not found, got %v", spanNames(spans))
|
||||
}
|
||||
|
||||
attrs := attributeMap(rpcSpan.Attributes)
|
||||
if attrs["rpc.system"] != "jsonrpc" {
|
||||
t.Errorf("expected rpc.system=jsonrpc, got %v", attrs["rpc.system"])
|
||||
}
|
||||
if attrs["rpc.method"] != "test_echo" {
|
||||
t.Errorf("expected rpc.method=test_echo, got %v", attrs["rpc.method"])
|
||||
}
|
||||
if _, ok := attrs["rpc.id"]; !ok {
|
||||
t.Errorf("expected rpc.id attribute to be set")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTracingBatchHTTP verifies that RPC spans are emitted for batched JSON-RPC calls over HTTP.
|
||||
func TestTracingBatchHTTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
server, tracer, exporter := newTracingServer(t)
|
||||
|
||||
httpsrv := httptest.NewServer(server)
|
||||
t.Cleanup(httpsrv.Close)
|
||||
|
||||
client, err := DialHTTP(httpsrv.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to dial: %v", err)
|
||||
}
|
||||
t.Cleanup(client.Close)
|
||||
|
||||
batch := []BatchElem{
|
||||
{
|
||||
Method: "test_echo",
|
||||
Args: []any{"hello", 42, &echoArgs{S: "world"}},
|
||||
Result: new(echoResult),
|
||||
},
|
||||
{
|
||||
Method: "test_echo",
|
||||
Args: []any{"your", 7, &echoArgs{S: "mom"}},
|
||||
Result: new(echoResult),
|
||||
},
|
||||
}
|
||||
if err := client.BatchCall(batch); err != nil {
|
||||
t.Fatalf("batch RPC call failed: %v", err)
|
||||
}
|
||||
|
||||
// Flush and verify we emitted at least one rpc.call span with batch=true.
|
||||
if err := tracer.ForceFlush(context.Background()); err != nil {
|
||||
t.Fatalf("failed to flush: %v", err)
|
||||
}
|
||||
spans := exporter.GetSpans()
|
||||
if len(spans) == 0 {
|
||||
t.Fatal("no spans were emitted")
|
||||
}
|
||||
|
||||
var found int
|
||||
for i := range spans {
|
||||
if spans[i].Name != "rpc.call" {
|
||||
continue
|
||||
}
|
||||
attrs := attributeMap(spans[i].Attributes)
|
||||
if attrs["rpc.system"] == "jsonrpc" &&
|
||||
attrs["rpc.method"] == "test_echo" &&
|
||||
attrs["rpc.batch"] == "true" {
|
||||
found++
|
||||
}
|
||||
}
|
||||
if found != len(batch) {
|
||||
t.Fatalf("expected %d matching batch spans, got %d", len(batch), found)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue