diff --git a/CHANGELOG.md b/CHANGELOG.md index b47c7d396..5ed5f5f22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## Changelog * [CHANGE] Updated the minimum required Go version to 1.20 and update Drone config #420 +* [CHANGE] Upgraded Tracer: Transitioned from OpenTracing to OpenTelemetry using a seamless bridge integration. #345 +* [CHANGE] Updated the minimum required Go version to 1.19 to ensure compatibility with OpenTelemetry version 1.14.0. #350 * [CHANGE] Change `WaitRingStability` and `WaitInstanceState` methods signature to rely on `ReadRing` instead. #251 * [CHANGE] Added new `-consul.cas-retry-delay` flag. It has a default value of `1s`, while previously there was no delay between retries. #178 * [CHANGE] Flagext: `DayValue` now always uses UTC when parsing or displaying dates. #71 diff --git a/go.mod b/go.mod index 485c4e797..7fcd898e3 100644 --- a/go.mod +++ b/go.mod @@ -27,8 +27,6 @@ require ( github.com/hashicorp/golang-lru/v2 v2.0.5 github.com/hashicorp/memberlist v0.3.1 github.com/miekg/dns v1.1.50 - github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e - github.com/opentracing-contrib/go-stdlib v1.0.0 github.com/opentracing/opentracing-go v1.2.0 github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 @@ -38,16 +36,23 @@ require ( github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97 github.com/sercand/kuberesolver/v5 v5.1.1 github.com/soheilhy/cmux v0.1.5 - github.com/stretchr/testify v1.8.1 - github.com/uber/jaeger-client-go v2.28.0+incompatible - github.com/uber/jaeger-lib v2.2.0+incompatible + github.com/stretchr/testify v1.8.4 go.etcd.io/etcd/api/v3 v3.5.0 go.etcd.io/etcd/client/pkg/v3 v3.5.0 go.etcd.io/etcd/client/v3 v3.5.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.44.0 + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.44.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 + go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 + go.opentelemetry.io/contrib/samplers/jaegerremote v0.11.0 + go.opentelemetry.io/otel v1.18.0 + go.opentelemetry.io/otel/exporters/jaeger v1.17.0 + go.opentelemetry.io/otel/sdk v1.17.0 + go.opentelemetry.io/otel/trace v1.18.0 go.uber.org/atomic v1.10.0 go.uber.org/goleak v1.2.0 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 - golang.org/x/net v0.14.0 + golang.org/x/net v0.15.0 golang.org/x/sync v0.3.0 golang.org/x/time v0.1.0 google.golang.org/grpc v1.59.0 @@ -60,13 +65,14 @@ require ( github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/gomodule/redigo v1.8.9 // indirect github.com/google/btree v1.0.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect @@ -90,13 +96,14 @@ require ( github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect + go.opentelemetry.io/otel/metric v1.18.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.17.0 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.13.0 // indirect golang.org/x/mod v0.8.0 // indirect golang.org/x/oauth2 v0.11.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.6.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect diff --git a/go.sum b/go.sum index f1f186ef7..8bb9663f4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= +cloud.google.com/go/compute v1.23.0 h1:tP41Zoavr8ptEqaW6j+LQOnyBBhO7OkOMAGrgLopTwY= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -44,8 +47,7 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -65,6 +67,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW64qjjJq8M2u4dxNaBiDfKK+z/3eGDpXEQhc= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -86,6 +89,11 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/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.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -144,7 +152,6 @@ github.com/grafana/memberlist v0.3.1-0.20220714140823-09ffed8adbbe h1:yIXAAbLswn github.com/grafana/memberlist v0.3.1-0.20220714140823-09ffed8adbbe/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hashicorp/consul/api v1.15.3 h1:WYONYL2rxTXtlekAqblR2SCdJsizMDIj/uXb5wNy9zU= github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY= github.com/hashicorp/consul/sdk v0.11.0 h1:HRzj8YSCln2yGgCumN5CL8lYlD3gBurnervJRJAZyC4= @@ -252,11 +259,6 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.24.0 h1:+0glovB9Jd6z3VR+ScSwQqXVTIfJcGA9UBM8yzQxhqg= github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= -github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= -github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= -github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= -github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -327,13 +329,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/uber/jaeger-client-go v2.28.0+incompatible h1:G4QSBfvPKvg5ZM2j9MrJFdfI5iSljY/WnJqOGFao6HI= -github.com/uber/jaeger-client-go v2.28.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/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.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -346,6 +344,26 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.0 h1:2aQv6F436YnN7I4VbI8PPYrBhu+SmrTaADcf8Mi/ go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.44.0 h1:b8xjZxHbLrXAum4SxJd1Rlm7Y/fKaB+6ACI7/e5EfSA= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.44.0/go.mod h1:1ei0a32xOGkFoySu7y1DAHfcuIhC0pNZpvY2huXuMy4= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.44.0 h1:ewRgsETI7b5nPCK3FqKdY9mFR/9ZwtexwC26//Srjn0= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.44.0/go.mod h1:+BrAX3hlRmkYIKl2e/eSRaKLkClDTY19gzegkQ+KeEQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 h1:KfYpVmrjI7JuToy5k8XV3nkapjWx48k4E4JOtVstzQI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0/go.mod h1:SeQhzAEccGVZVEy7aH87Nh0km+utSpo1pTv6eMMop48= +go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 h1:Zbpbmwav32Ea5jSotpmkWEl3a6Xvd4tw/3xxGO1i05Y= +go.opentelemetry.io/contrib/propagators/jaeger v1.17.0/go.mod h1:tcTUAlmO8nuInPDSBVfG+CP6Mzjy5+gNV4mPxMbL0IA= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.11.0 h1:P3JkQvs0s4Ww3hPb+jWFW9N6A0ioew7WwGTyqwgeofs= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.11.0/go.mod h1:U+s0mJMfMC2gicc4WEgZ50JSR+5DhOIjcvFOCVAe8/U= +go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs= +go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= +go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= +go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= +go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= +go.opentelemetry.io/otel/sdk v1.17.0 h1:FLN2X66Ke/k5Sg3V623Q7h7nt3cHXaW1FOvKKrW0IpE= +go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZFNUBlSgmDFQ= +go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10= +go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -361,8 +379,8 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= @@ -388,7 +406,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -402,8 +419,8 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -458,8 +475,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -470,8 +487,8 @@ golang.org/x/text v0.3.5/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.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -512,7 +529,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go. google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= diff --git a/grpcclient/instrumentation.go b/grpcclient/instrumentation.go index f721c9dcf..157301999 100644 --- a/grpcclient/instrumentation.go +++ b/grpcclient/instrumentation.go @@ -1,8 +1,6 @@ package grpcclient import ( - otgrpc "github.com/opentracing-contrib/go-grpc" - "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" diff --git a/httpgrpc/server/server.go b/httpgrpc/server/server.go index 93c140697..2d4ba8366 100644 --- a/httpgrpc/server/server.go +++ b/httpgrpc/server/server.go @@ -15,17 +15,14 @@ import ( "net/url" "strings" - "github.com/go-kit/log/level" - "github.com/opentracing/opentracing-go" "github.com/sercand/kuberesolver/v5" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - "go.opentelemetry.io/otel" + "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "github.com/grafana/dskit/httpgrpc" - "github.com/grafana/dskit/log" "github.com/grafana/dskit/middleware" ) @@ -201,12 +198,8 @@ func WriteError(w http.ResponseWriter, err error) { // ServeHTTP implements http.Handler func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if tracer := otel.Tracer("github.com/grafana/mimir"); tracer != nil { - span := trace.SpanFromContext(r.Context()) - if err := tracer.Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)); err != nil { - level.Warn(log.Global()).Log("msg", "failed to inject tracing headers into request", "err", err) - } - + if span := trace.SpanFromContext(r.Context()); span.SpanContext().IsValid() { + otelhttptrace.Inject(r.Context(), r) } req, err := HTTPRequest(r) diff --git a/httpgrpc/server/server_test.go b/httpgrpc/server/server_test.go index 2d4899640..878e2dfac 100644 --- a/httpgrpc/server/server_test.go +++ b/httpgrpc/server/server_test.go @@ -9,18 +9,23 @@ import ( "context" "errors" "fmt" + "log" "net" "net/http" "net/http/httptest" "testing" - opentracing "github.com/opentracing/opentracing-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - jaegercfg "github.com/uber/jaeger-client-go/config" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/baggage" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + jaegerpropagator "go.opentelemetry.io/contrib/propagators/jaeger" + sdktrace "go.opentelemetry.io/otel/sdk/trace" "github.com/grafana/dskit/httpgrpc" "github.com/grafana/dskit/middleware" @@ -210,15 +215,26 @@ func TestParseURL(t *testing.T) { } func TestTracePropagation(t *testing.T) { - jaeger := jaegercfg.Configuration{} - closer, err := jaeger.InitGlobalTracer("test") - require.NoError(t, err) - defer closer.Close() + tp := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.AlwaysSample()), + sdktrace.WithBatcher(tracetest.NewInMemoryExporter()), + ) + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator([]propagation.TextMapPropagator{ + propagation.TraceContext{}, propagation.Baggage{}, + jaegerpropagator.Jaeger{}, + }...)) + + defer func() { + if err := tp.Shutdown(context.Background()); err != nil { + log.Printf("Error shutting down tracer provider: %v", err) + } + }() server, err := newTestServer(t, middleware.Tracer{}.Wrap( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - span := trace.SpanFromContext(r.Context()) - _, err := fmt.Fprint(w, span.BaggageItem("name")) + md, _ := metadata.FromIncomingContext(r.Context()) + _, err := fmt.Fprint(w, md.Get("baggage")[0]) require.NoError(t, err) }), )) @@ -232,13 +248,19 @@ func TestTracePropagation(t *testing.T) { req, err := http.NewRequest("GET", "/hello", &bytes.Buffer{}) require.NoError(t, err) - ctx, sp := otel.Tracer("github.com/grafana/mimir").Start(context.Background(), "Test") - sp.SetBaggageItem("name", "world") + ctx, sp := otel.Tracer("").Start(req.Context(), "Test") + defer sp.End() + meb, err := baggage.NewMember("name", "world") + require.NoError(t, err) + bg, err := baggage.New(meb) + require.NoError(t, err) + ctx = baggage.ContextWithBaggage(ctx, bg) req = req.WithContext(user.InjectOrgID(ctx, "1")) recorder := httptest.NewRecorder() + client.ServeHTTP(recorder, req) - assert.Equal(t, "world", recorder.Body.String()) + assert.Equal(t, "name=world", recorder.Body.String()) assert.Equal(t, 200, recorder.Code) } diff --git a/instrument/instrument.go b/instrument/instrument.go index 00bd6e160..806981a00 100644 --- a/instrument/instrument.go +++ b/instrument/instrument.go @@ -10,11 +10,9 @@ import ( "context" "time" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" - attribute "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "github.com/grafana/dskit/grpcutil" @@ -74,7 +72,7 @@ func (c *HistogramCollector) After(ctx context.Context, method, statusCode strin // 'histogram' parameter must be castable to prometheus.ExemplarObserver or function will panic // (this will always work for a HistogramVec). func ObserveWithExemplar(ctx context.Context, histogram prometheus.Observer, seconds float64) { - if traceID, ok := tracing.ExtractSampledTraceID(ctx); ok { + if traceID, ok := tracing.ExtractOtelSampledTraceID(ctx); ok { histogram.(prometheus.ExemplarObserver).ObserveWithExemplar( seconds, prometheus.Labels{"traceID": traceID}, @@ -154,19 +152,18 @@ func (c *JobCollector) After(_ context.Context, method, statusCode string, start // CollectedRequest runs a tracked request. It uses the given Collector to monitor requests. // // If `f` returns no error we log "200" as status code, otherwise "500". Pass in a function -// for `toStatusCode` to overwrite this behaviour. It will also emit an OpenTracing span if +// for `toStatusCode` to overwrite this behaviour. It will also emit an otel span if // you have a global tracer configured. func CollectedRequest(ctx context.Context, method string, col Collector, toStatusCode func(error) string, f func(context.Context) error) error { if toStatusCode == nil { toStatusCode = ErrorCode } - newCtx, sp := otel.Tracer("github.com/grafana/mimir").Start(ctx, method) - ext.SpanKindRPCClient.Set(sp) + newCtx, sp := otel.Tracer("").Start(ctx, method, trace.WithSpanKind(trace.SpanKindClient)) if userID, err := user.ExtractUserID(ctx); err == nil { - sp.SetTag("user", userID) + sp.SetAttributes(attribute.String("user", userID)) } if orgID, err := user.ExtractOrgID(ctx); err == nil { - sp.SetTag("organization", orgID) + sp.SetAttributes(attribute.String("organization", orgID)) } start := time.Now() @@ -176,11 +173,11 @@ func CollectedRequest(ctx context.Context, method string, col Collector, toStatu if err != nil { if !grpcutil.IsCanceled(err) { - ext.Error.Set(sp, true) + sp.RecordError(err, trace.WithStackTrace(true)) } - sp.AddEvent("", trace.WithAttributes(attribute.Error(err))) + sp.AddEvent("error", trace.WithAttributes(attribute.String("msg", err.Error()))) } - sp.Finish() + sp.End() return err } diff --git a/middleware/http_tracing.go b/middleware/http_tracing.go index c11129587..d6fdfa8ca 100644 --- a/middleware/http_tracing.go +++ b/middleware/http_tracing.go @@ -9,15 +9,12 @@ import ( "net/http" "github.com/gorilla/mux" - "github.com/opentracing-contrib/go-stdlib/nethttp" - "github.com/opentracing/opentracing-go/ext" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) -// Dummy dependency to enforce that we have a nethttp version newer -// than the one which implements Websockets. (No semver on nethttp) -var _ = nethttp.MWURLTagFunc - // Tracer is a middleware which traces incoming requests. type Tracer struct { RouteMatcher RouteMatcher @@ -26,24 +23,36 @@ type Tracer struct { // Wrap implements Interface func (t Tracer) Wrap(next http.Handler) http.Handler { - options := []nethttp.MWOption{ - nethttp.OperationNameFunc(makeHTTPOperationNameFunc(t.RouteMatcher)), - nethttp.MWSpanObserver(func(sp trace.Span, r *http.Request) { - // add a tag with the client's user agent to the span - userAgent := r.Header.Get("User-Agent") - if userAgent != "" { - sp.SetTag("http.user_agent", userAgent) - } - - // add a tag with the client's sourceIPs to the span, if a - // SourceIPExtractor is given. - if t.SourceIPs != nil { - sp.SetTag("sourceIPs", t.SourceIPs.Get(r)) - } - }), + options := []otelhttp.Option{ + otelhttp.WithSpanNameFormatter(makeHTTPOperationNameFunc(t.RouteMatcher)), } - return nethttp.Middleware(opentracing.GlobalTracer(), next, options...) + // Apply OpenTelemetry tracing middleware first + tracingMiddleware := otelhttp.NewHandler(next, "http.tracing", options...) + + // Wrap the 'tracingMiddleware' to capture its execution + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + labeler, _ := otelhttp.LabelerFromContext(r.Context()) + // add a tag with the client's user agent to the span + userAgent := r.Header.Get("User-Agent") + if userAgent != "" { + labeler.Add(attribute.String("http.user_agent", userAgent)) + } + + labeler.Add(attribute.String("http.url", r.URL.Path)) + labeler.Add(attribute.String("http.method", r.Method)) + + labeler.Add(attribute.String("headers", fmt.Sprintf("%v", r.Header))) + // add a tag with the client's sourceIPs to the span, if a + // SourceIPExtractor is given. + if t.SourceIPs != nil { + labeler.Add(attribute.String("sourceIPs", t.SourceIPs.Get(r))) + } + + tracingMiddleware.ServeHTTP(w, r) + }) + + return handler } // HTTPGRPCTracer is a middleware which traces incoming httpgrpc requests. @@ -75,19 +84,14 @@ func InitHTTPGRPCMiddleware(router *mux.Router) *mux.Router { // By default, the server-side tracing spans for the httpgrpc.HTTP/Handle gRPC method // have no data about the wrapped HTTP request being handled. // -// HTTPGRPCTracer.Wrap starts a child span with span name and tags following the approach in -// Tracer.Wrap's usage of opentracing-contrib/go-stdlib/nethttp.Middleware -// and attaches the HTTP server span tags to the parent httpgrpc.HTTP/Handle gRPC span, allowing +// HTTPGRPCTracer.Wrap starts a child span with span name and tags and +// attaches the HTTP server span tags to the parent httpgrpc.HTTP/Handle gRPC span, allowing // tracing tooling to differentiate the HTTP requests represented by the httpgrpc.HTTP/Handle spans. -// -// opentracing-contrib/go-stdlib/nethttp.Middleware could not be used here -// as it does not expose options to access and tag the incoming parent span. func (hgt HTTPGRPCTracer) Wrap(next http.Handler) http.Handler { httpOperationNameFunc := makeHTTPOperationNameFunc(hgt.RouteMatcher) + fn := func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - tracer := opentracing.GlobalTracer() - parentSpan := trace.SpanFromContext(ctx) // extract relevant span & tag data from request @@ -97,45 +101,39 @@ func (hgt HTTPGRPCTracer) Wrap(next http.Handler) http.Handler { userAgent := r.Header.Get("User-Agent") // tag parent httpgrpc.HTTP/Handle server span, if it exists - if parentSpan != nil { - parentSpan.SetTag(string(ext.HTTPUrl), urlPath) - parentSpan.SetTag(string(ext.HTTPMethod), method) - parentSpan.SetTag("http.route", matchedRoute) - parentSpan.SetTag("http.user_agent", userAgent) + if parentSpan.SpanContext().IsValid() { + parentSpan.SetAttributes(attribute.String("http.url", urlPath)) + parentSpan.SetAttributes(attribute.String("http.method", method)) + parentSpan.SetAttributes(attribute.String("http.route", matchedRoute)) + parentSpan.SetAttributes(attribute.String("http.user_agent", userAgent)) } - // create and start child HTTP span - // mirroring opentracing-contrib/go-stdlib/nethttp.Middleware span name and tags - childSpanName := httpOperationNameFunc(r) - startSpanOpts := []opentracing.StartSpanOption{ - ext.SpanKindRPCServer, - opentracing.Tag{Key: string(ext.Component), Value: "net/http"}, - opentracing.Tag{Key: string(ext.HTTPUrl), Value: urlPath}, - opentracing.Tag{Key: string(ext.HTTPMethod), Value: method}, - opentracing.Tag{Key: "http.route", Value: matchedRoute}, - opentracing.Tag{Key: "http.user_agent", Value: userAgent}, - } - if parentSpan != nil { - startSpanOpts = append( - startSpanOpts, - opentracing.SpanReference{ - Type: opentracing.ChildOfRef, - ReferencedContext: parentSpan.Context(), - }) + // create and start child HTTP span and set span name and attributes + childSpanName := httpOperationNameFunc("", r) + + startSpanOpts := []trace.SpanStartOption{ + trace.WithSpanKind(trace.SpanKindServer), + trace.WithAttributes( + attribute.String("component", "net/http"), + attribute.String("http.method", method), + attribute.String("http.url", urlPath), + attribute.String("http.route", matchedRoute), + attribute.String("http.user_agent", userAgent), + ), } + var childSpan trace.Span + ctx, childSpan = otel.Tracer("").Start(ctx, childSpanName, startSpanOpts...) + defer childSpan.End() - childSpan := tracer.StartSpan(childSpanName, startSpanOpts...) - defer childSpan.Finish() - - r = r.WithContext(opentracing.ContextWithSpan(r.Context(), childSpan)) + r = r.WithContext(trace.ContextWithSpan(ctx, childSpan)) next.ServeHTTP(w, r) } return http.HandlerFunc(fn) } -func makeHTTPOperationNameFunc(routeMatcher RouteMatcher) func(r *http.Request) string { - return func(r *http.Request) string { +func makeHTTPOperationNameFunc(routeMatcher RouteMatcher) func(_ string, r *http.Request) string { + return func(_ string, r *http.Request) string { op := getRouteName(routeMatcher, r) if op == "" { return "HTTP " + r.Method diff --git a/middleware/logging.go b/middleware/logging.go index aeb15cc6b..a76c73e72 100644 --- a/middleware/logging.go +++ b/middleware/logging.go @@ -56,7 +56,7 @@ func NewLogMiddleware(log log.Logger, logRequestHeaders bool, logRequestAtInfoLe // logWithRequest information from the request and context as fields. func (l Log) logWithRequest(r *http.Request) log.Logger { localLog := l.Log - traceID, ok := tracing.ExtractTraceID(r.Context()) + traceID, ok := tracing.ExtractOtelTraceID(r.Context()) if ok { localLog = log.With(localLog, "traceID", traceID) } diff --git a/otel.gopatch b/otel.gopatch deleted file mode 100644 index e4db1bf01..000000000 --- a/otel.gopatch +++ /dev/null @@ -1,155 +0,0 @@ -@@ -var a expression -var b expression -var s identifier -var t identifier -@@ --s, t := opentracing.StartSpanFromContext(a,b) --... -- defer s.Finish() -+import "go.opentelemetry.io/otel" -+t, s := otel.Tracer("github.com/grafana/mimir").Start(a,b) -+defer s.End() - -@@ -var a expression -var s identifier -@@ --s := opentracing.SpanFromContext(a) -+import "go.opentelemetry.io/otel/trace" -+s := trace.SpanFromContext(a) - -@@ -var foo,x identifier -@@ --import foo "github.com/opentracing/opentracing-go/log" -+import foo "go.opentelemetry.io/otel/attribute" -foo.x - -@@ -@@ -- otlog -+ attribute - -@@ -var span identifier -var x expression -@@ -- span.LogFields(...) -+import "go.opentelemetry.io/otel/trace" -+ span.AddEvent("", trace.WithAttributes(...)) - -@@ -var sp identifier -var a expression -@@ --if sp := opentracing.SpanFromContext(a); sp != nil { --... --} -+import "go.opentelemetry.io/otel/trace" -+sp := trace.SpanFromContext(a) -+... - -@@ -var ctx identifier -var a expression -var b expression -@@ --ctx = opentracing.ContextWithSpan(a, b) -+import "go.opentelemetry.io/otel/trace" -+ctx = trace.ContextWithSpan(a, b) - -@@ -var otgrpc identifier -@@ --import otgrpc "github.com/opentracing-contrib/go-grpc" -+import "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - --otgrpc.OpenTracingClientInterceptor(...) -+otelgrpc.UnaryClientInterceptor() - -@@ -var otgrpc identifier -@@ --otgrpc.OpenTracingStreamClientInterceptor(...) -+otelgrpc.StreamClientInterceptor() - -@@ -var otgrpc identifier -@@ --otgrpc.OpenTracingStreamServerInterceptor(...) -+otelgrpc.StreamServerInterceptor() - -@@ -@@ --opentracing.SpanFromContext(...) -+import "go.opentelemetry.io/otel/trace" -+trace.SpanFromContext(...) - -@@ -var i identifier -@@ --var i opentracing.Span -+import "go.opentelemetry.io/otel/trace" -+var i trace.Span - -@@ -var i expression -var j expression -@@ --i, j = opentracing.StartSpanFromContext(...) -+import "go.opentelemetry.io/otel/trace" -+j, i = trace.Tracer("github.com/grafana/mimir").Start(...) - -@@ -var i identifier -var j identifier -@@ --i, j = opentracing.StartSpanFromContext(...) -+import "go.opentelemetry.io/otel" -+j, i = otel.Tracer("github.com/grafana/mimir").Start(...) - -@@ -var i identifier -var j identifier -@@ --i, j := opentracing.StartSpanFromContext(...) -+import "go.opentelemetry.io/otel" -+j, i := otel.Tracer("github.com/grafana/mimir").Start(...) - -@@ -@@ --import "github.com/opentracing/opentracing-go" --opentracing.Span -+import "go.opentelemetry.io/otel/trace" -+trace.Span - -@@ -@@ - --import "github.com/opentracing-contrib/go-stdlib/nethttp" -+import "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - --nethttp.Transport{} -+otelhttp.Transport{} - -@@ -@@ - --import "github.com/opentracing/opentracing-go" -+import "go.opentelemetry.io/otel" - --opentracing.GlobalTracer() -+otel.Tracer("github.com/grafana/mimir") - -@@ -var foo,x identifier -@@ --import foo "github.com/opentracing/opentracing-go" -+import foo "go.opentelemetry.io/otel" -foo.x - -@@ -@@ --opentracing.Span -+trace.Span diff --git a/server/server.go b/server/server.go index c2a030edf..5eae2b98d 100644 --- a/server/server.go +++ b/server/server.go @@ -20,13 +20,12 @@ import ( gokit_log "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/gorilla/mux" - otgrpc "github.com/opentracing-contrib/go-grpc" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/common/config" "github.com/prometheus/exporter-toolkit/web" "github.com/soheilhy/cmux" - "go.opentelemetry.io/otel" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "golang.org/x/net/netutil" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -350,7 +349,7 @@ func newServer(cfg Config, metrics *Metrics) (*Server, error) { } grpcMiddleware := []grpc.UnaryServerInterceptor{ serverLog.UnaryServerInterceptor, - otgrpc.OpenTracingServerInterceptor(otel.Tracer("github.com/grafana/mimir")), + otelgrpc.UnaryServerInterceptor(), middleware.UnaryServerInstrumentInterceptor(metrics.RequestDuration), } grpcMiddleware = append(grpcMiddleware, cfg.GRPCMiddleware...) diff --git a/server/server_tracing_test.go b/server/server_tracing_test.go index 33a8f58e2..7b8ea4803 100644 --- a/server/server_tracing_test.go +++ b/server/server_tracing_test.go @@ -2,7 +2,9 @@ package server import ( "bytes" + "context" "fmt" + "log" "net" "net/http" "net/url" @@ -10,71 +12,37 @@ import ( "testing" "github.com/gorilla/mux" - "github.com/opentracing/opentracing-go/ext" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" - "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "github.com/grafana/dskit/httpgrpc" httpgrpcServer "github.com/grafana/dskit/httpgrpc/server" - "github.com/grafana/dskit/log" + dskitlog "github.com/grafana/dskit/log" "github.com/grafana/dskit/middleware" ) -// testObserver implements jaeger.ContribSpanObserver to collect an emitted span's attributes -type testSpanObserver struct { - OpName string - Tags map[string]interface{} - References []opentracing.SpanReference -} - -func (tso testSpanObserver) OnSetOperationName(operationName string) { - tso.OpName = operationName //nolint:staticcheck // SA4005 -} -func (tso testSpanObserver) OnSetTag(key string, value interface{}) { - tso.Tags[key] = value -} -func (tso testSpanObserver) OnFinish(_ opentracing.FinishOptions) {} - -// testObserver implements jaeger.ContribObserver to collect SpanObservers as they are emitted -type testObserver struct { - SpanObservers []testSpanObserver -} - -func (to *testObserver) OnStartSpan( - _ trace.Span, - operationName string, - options opentracing.StartSpanOptions, -) (jaeger.ContribSpanObserver, bool) { - spanObserver := testSpanObserver{ - OpName: operationName, - Tags: options.Tags, - References: options.References, - } - to.SpanObservers = append(to.SpanObservers, spanObserver) - return spanObserver, true -} - // assertTracingSpans tests if expected spans and tags were recorded by tracing SpanObservers func assertTracingSpans( t *testing.T, - spanObservers []testSpanObserver, + spanObservers []sdktrace.ReadOnlySpan, expectedTagsByOpName map[string]map[string]string, ) { var observedSpanOpNames []string for _, spanObserver := range spanObservers { // assert expected tag values for observed span, if any - if expectedTags, ok := expectedTagsByOpName[spanObserver.OpName]; ok { - for tagKey, tagValue := range spanObserver.Tags { - if expectedTagValue, ok := expectedTags[tagKey]; ok { - require.Equal(t, expectedTagValue, tagValue) + if expectedTags, ok := expectedTagsByOpName[spanObserver.Name()]; ok { + for _, kv := range spanObserver.Attributes() { + if expectedTagValue, ok := expectedTags[string(kv.Key)]; ok { + require.Equal(t, expectedTagValue, kv.Value.AsString()) } } } // collect observed span operation names - observedSpanOpNames = append(observedSpanOpNames, spanObserver.OpName) + observedSpanOpNames = append(observedSpanOpNames, spanObserver.Name()) } for opName := range expectedTagsByOpName { // assert all expected operations were observed @@ -83,7 +51,6 @@ func assertTracingSpans( } func TestHTTPGRPCTracing(t *testing.T) { - httpPort := 9099 httpAddress := "127.0.0.1" @@ -109,17 +76,17 @@ func TestHTTPGRPCTracing(t *testing.T) { // regardless of whether the request is routed through the gRPC Handle method first expectedOpNameHelloHTTPSpan := "HTTP " + httpMethod + " - " + expectedHelloRouteLabel expectedTagsHelloHTTPSpan := map[string]string{ - string(ext.Component): "net/http", - string(ext.HTTPUrl): helloRouteURL.Path, - string(ext.HTTPMethod): httpMethod, - "http.route": helloRouteName, + "component": "net/http", + "http.url": helloRouteURL.Path, + "http.method": httpMethod, + "http.route": helloRouteName, } expectedOpNameHelloPathParamHTTPSpan := "HTTP " + httpMethod + " - " + expectedHelloPathParamRouteLabel expectedTagsHelloPathParamHTTPSpan := map[string]string{ - string(ext.Component): "net/http", - string(ext.HTTPUrl): helloPathParamRouteURL.Path, - string(ext.HTTPMethod): httpMethod, - "http.route": expectedHelloPathParamRouteLabel, + "component": "net/http", + "http.url": helloPathParamRouteURL.Path, + "http.method": httpMethod, + "http.route": expectedHelloPathParamRouteLabel, } tests := map[string]struct { @@ -137,11 +104,11 @@ func TestHTTPGRPCTracing(t *testing.T) { routeLabel: expectedHelloRouteLabel, reqURL: helloRouteURLRaw, expectedTagsByOpName: map[string]map[string]string{ - "/httpgrpc.HTTP/Handle": { - string(ext.Component): "gRPC", - string(ext.HTTPUrl): helloRouteURL.Path, - string(ext.HTTPMethod): httpMethod, - "http.route": helloRouteName, + "httpgrpc.HTTP/Handle": { + "component": "gRPC", + "http.url": helloRouteURL.Path, + "http.method": httpMethod, + "http.route": helloRouteName, }, expectedOpNameHelloHTTPSpan: expectedTagsHelloHTTPSpan, }, @@ -163,11 +130,11 @@ func TestHTTPGRPCTracing(t *testing.T) { routeLabel: expectedHelloPathParamRouteLabel, reqURL: helloPathParamRouteURLRaw, expectedTagsByOpName: map[string]map[string]string{ - "/httpgrpc.HTTP/Handle": { - string(ext.Component): "gRPC", - string(ext.HTTPUrl): helloPathParamRouteURL.Path, - string(ext.HTTPMethod): httpMethod, - "http.route": expectedHelloPathParamRouteLabel, + "httpgrpc.HTTP/Handle": { + "component": "gRPC", + "http.url": helloPathParamRouteURL.Path, + "http.method": httpMethod, + "http.route": expectedHelloPathParamRouteLabel, }, expectedOpNameHelloPathParamHTTPSpan: expectedTagsHelloPathParamHTTPSpan, }, @@ -186,15 +153,13 @@ func TestHTTPGRPCTracing(t *testing.T) { for testName, test := range tests { t.Run(testName, func(t *testing.T) { - - observer := testObserver{} - tracer, closer := jaeger.NewTracer( - "test", - jaeger.NewConstSampler(true), - jaeger.NewInMemoryReporter(), - jaeger.TracerOptions.ContribObserver(&observer), + exp := tracetest.NewInMemoryExporter() + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp), + sdktrace.WithSampler(sdktrace.AlwaysSample()), ) - opentracing.SetGlobalTracer(tracer) + otel.SetTracerProvider(tp) + ctx := context.Background() var cfg Config cfg.HTTPListenAddress = httpAddress @@ -205,7 +170,7 @@ func TestHTTPGRPCTracing(t *testing.T) { cfg.GRPCServerMaxSendMsgSize = 4 * 1024 * 1024 cfg.Router = middleware.InitHTTPGRPCMiddleware(mux.NewRouter()) cfg.MetricsNamespace = "testing_httpgrpc_tracing_" + middleware.MakeLabelValue(testName) - var lvl log.Level + var lvl dskitlog.Level require.NoError(t, lvl.Set("info")) cfg.LogLevel = lvl @@ -258,11 +223,17 @@ func TestHTTPGRPCTracing(t *testing.T) { require.NoError(t, err) } - assertTracingSpans(t, observer.SpanObservers, test.expectedTagsByOpName) + tp.ForceFlush(ctx) + assertTracingSpans(t, exp.GetSpans().Snapshots(), test.expectedTagsByOpName) conn.Close() server.Shutdown() - closer.Close() + defer func() { + if err := tp.Shutdown(ctx); err != nil { + log.Printf("Error shutting down tracer provider: %v", err) + } + }() + }) } } diff --git a/spanlogger/spanlogger.go b/spanlogger/spanlogger.go index 49005336f..67e74a9af 100644 --- a/spanlogger/spanlogger.go +++ b/spanlogger/spanlogger.go @@ -2,17 +2,17 @@ package spanlogger import ( "context" + "fmt" - "go.opentelemetry.io/otel" - opentracing "go.opentelemetry.io/otel" - attribute "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" "go.uber.org/atomic" // Really just need sync/atomic but there is a lint rule preventing it. "github.com/go-kit/log" "github.com/go-kit/log/level" + "go.opentelemetry.io/otel" + attribute "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "github.com/grafana/dskit/tracing" - "github.com/opentracing/opentracing-go/ext" ) type loggerCtxMarker struct{} @@ -48,11 +48,11 @@ type SpanLogger struct { // New makes a new SpanLogger with a log.Logger to send logs to. The provided context will have the logger attached // to it and can be retrieved with FromContext. func New(ctx context.Context, logger log.Logger, method string, resolver TenantResolver, kvps ...interface{}) (*SpanLogger, context.Context) { - ctx, span := otel.Tracer("github.com/grafana/mimir").Start(ctx, method) + ctx, span := otel.Tracer("").Start(ctx, method) if ids, err := resolver.TenantIDs(ctx); err == nil && len(ids) > 0 { - span.SetTag(TenantIDsTagName, ids) + span.SetAttributes(attribute.StringSlice(TenantIDsTagName, ids)) } - _, sampled := tracing.ExtractSampledTraceID(ctx) + _, sampled := tracing.ExtractOtelSampledTraceID(ctx) l := &SpanLogger{ ctx: ctx, resolver: resolver, @@ -78,13 +78,10 @@ func FromContext(ctx context.Context, fallback log.Logger, resolver TenantResolv if !ok { logger = fallback } - sampled := false + sp := trace.SpanFromContext(ctx) - if sp == nil { - sp = opentracing.NoopTracer{}.StartSpan("noop") - } else { - _, sampled = tracing.ExtractSampledTraceID(ctx) - } + _, sampled := tracing.ExtractOtelSampledTraceID(ctx) + return &SpanLogger{ ctx: ctx, baseLogger: logger, @@ -106,6 +103,7 @@ func debugEnabled(logger log.Logger) bool { // Log implements gokit's Logger interface; sends logs to underlying logger and // also puts the on the spans. +// Log is expecting attribute key values pairs func (s *SpanLogger) Log(kvps ...interface{}) error { s.getLogger().Log(kvps...) return s.spanLog(kvps...) @@ -127,21 +125,39 @@ func (s *SpanLogger) spanLog(kvps ...interface{}) error { if !s.sampled { return nil } - fields, err := attribute.InterleavedKVToFields(kvps...) + fields, err := convertKVToAttributes(kvps...) if err != nil { return err } - s.Span.LogFields(fields...) + s.Span.SetAttributes(fields...) return nil } +// convertKVToAttributes converts keyValues to a slice of attribute.KeyValue +func convertKVToAttributes(keyValues ...interface{}) ([]attribute.KeyValue, error) { + if len(keyValues)%2 != 0 { + return nil, fmt.Errorf("non-even keyValues len: %d", len(keyValues)) + } + fields := make([]attribute.KeyValue, len(keyValues)/2) + for i := 0; i*2 < len(keyValues); i++ { + key, ok := keyValues[i*2].(string) + if !ok { + return nil, fmt.Errorf("non-string key (pair #%d): %T", i, keyValues[i*2]) + } + value := keyValues[i*2+1] + // here to simplify the attribute value type, we convert everything into string + fields[i] = attribute.String(key, fmt.Sprintf("%v", value)) + } + return fields, nil +} + // Error sets error flag and logs the error on the span, if non-nil. Returns the err passed in. func (s *SpanLogger) Error(err error) error { if err == nil || !s.sampled { return err } - ext.Error.Set(s.Span, true) - s.Span.LogFields(attribute.Error(err)) + s.RecordError(err, trace.WithStackTrace(true)) + s.Span.AddEvent("error", trace.WithAttributes(attribute.String("msg", err.Error()))) return err } @@ -157,7 +173,7 @@ func (s *SpanLogger) getLogger() log.Logger { logger = log.With(logger, "user", userID) } - traceID, ok := tracing.ExtractSampledTraceID(s.ctx) + traceID, ok := tracing.ExtractOtelSampledTraceID(s.ctx) if ok { logger = log.With(logger, "traceID", traceID) } diff --git a/spanlogger/spanlogger_test.go b/spanlogger/spanlogger_test.go index b5786c933..27b3eb376 100644 --- a/spanlogger/spanlogger_test.go +++ b/spanlogger/spanlogger_test.go @@ -7,17 +7,23 @@ import ( "github.com/go-kit/log" "github.com/go-kit/log/level" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/mocktracer" "github.com/pkg/errors" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" "github.com/grafana/dskit/user" ) -func TestSpanLogger_Log(t *testing.T) { +func TestOtelSpanLogger_Log(t *testing.T) { logger := log.NewNopLogger() resolver := fakeResolver{} + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(tracetest.NewInMemoryExporter()), + ) + otel.SetTracerProvider(tp) span, ctx := New(context.Background(), logger, "test", resolver, "bar") _ = span.Log("foo") newSpan := FromContext(ctx, logger, resolver) @@ -29,13 +35,20 @@ func TestSpanLogger_Log(t *testing.T) { require.NoError(t, noSpan.Error(nil)) } -func TestSpanLogger_CustomLogger(t *testing.T) { +func TestOtelSpanLogger_CustomLogger(t *testing.T) { var logged [][]interface{} var logger funcLogger = func(keyvals ...interface{}) error { logged = append(logged, keyvals) return nil } resolver := fakeResolver{} + exp := tracetest.NewInMemoryExporter() + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp), + // Set the sampler to never sample so that traceID is not logged. + sdktrace.WithSampler(sdktrace.NeverSample()), + ) + otel.SetTracerProvider(tp) span, ctx := New(context.Background(), logger, "test", resolver) _ = span.Log("msg", "original spanlogger") @@ -53,25 +66,60 @@ func TestSpanLogger_CustomLogger(t *testing.T) { require.Equal(t, expect, logged) } -func TestSpanCreatedWithTenantTag(t *testing.T) { - mockSpan := createSpan(user.InjectOrgID(context.Background(), "team-a")) - - require.Equal(t, []string{"team-a"}, mockSpan.Tag(TenantIDsTagName)) +func TestOtelSpanCreatedWithTenantTag(t *testing.T) { + ctx := user.InjectOrgID(context.Background(), "team-a") + exp := tracetest.NewInMemoryExporter() + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp), + sdktrace.WithSampler(sdktrace.AlwaysSample()), + ) + otel.SetTracerProvider(tp) + + sl, ctx := New(ctx, log.NewNopLogger(), "get", fakeResolver{}) + sl.End() + // // Force flush to ensure spans are reported before the test ends. + tp.ForceFlush(ctx) + + require.Equal(t, 1, len(exp.GetSpans().Snapshots())) + require.Equal(t, + []attribute.KeyValue{attribute.StringSlice(TenantIDsTagName, []string{"team-a"})}, + exp.GetSpans().Snapshots()[0].Attributes()) } -func TestSpanCreatedWithoutTenantTag(t *testing.T) { - mockSpan := createSpan(context.Background()) - - _, exists := mockSpan.Tags()[TenantIDsTagName] - require.False(t, exists) +func TestOtelSpanCreatedWithoutTenantTag(t *testing.T) { + exp, _ := createOtelSpan(context.Background()) + require.Equal(t, 1, len(exp.GetSpans().Snapshots())) + exist := false + for _, kv := range exp.GetSpans().Snapshots()[0].Attributes() { + if kv.Key == TenantIDsTagName { + exist = true + } + } + require.False(t, exist) } -func createSpan(ctx context.Context) *mocktracer.MockSpan { - mockTracer := mocktracer.New() - opentracing.SetGlobalTracer(mockTracer) +// Using a no-op logger and no tracing provider, measure the overhead of a small log call. +func BenchmarkOtelSpanLogger(b *testing.B) { + _, sl := createOtelSpan(context.Background()) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = sl.Log("msg", "foo", "more", "data") + } +} - logger, _ := New(ctx, log.NewNopLogger(), "name", fakeResolver{}) - return logger.Span.(*mocktracer.MockSpan) +func createOtelSpan(ctx context.Context) (*tracetest.InMemoryExporter, *SpanLogger) { + exp := tracetest.NewInMemoryExporter() + tp := sdktrace.NewTracerProvider( + sdktrace.WithBatcher(exp), + sdktrace.WithSampler(sdktrace.AlwaysSample()), + ) + otel.SetTracerProvider(tp) + + sl, _ := New(ctx, log.NewNopLogger(), "get", fakeResolver{}) + sl.Span.End() + // Force flush to ensure spans are reported before the test ends. + tp.ForceFlush(ctx) + return exp, sl } type funcLogger func(keyvals ...interface{}) error diff --git a/tracing/oteltracing.go b/tracing/oteltracing.go new file mode 100644 index 000000000..d3f8b241c --- /dev/null +++ b/tracing/oteltracing.go @@ -0,0 +1,257 @@ +// Provenance-includes-location: https://github.com/weaveworks/common/blob/main/tracing/tracing.go +// Provenance-includes-license: Apache-2.0 +// Provenance-includes-copyright: Weaveworks Ltd. + +package tracing + +import ( + "context" + "fmt" + "io" + "net" + "net/url" + "os" + "strconv" + "strings" + + "github.com/pkg/errors" + jaegerpropagator "go.opentelemetry.io/contrib/propagators/jaeger" + "go.opentelemetry.io/contrib/samplers/jaegerremote" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + + //nolint:staticcheck + jaegerotel "go.opentelemetry.io/otel/exporters/jaeger" + "go.opentelemetry.io/otel/propagation" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" +) + +var ( + // ErrBlankTraceConfiguration is an error to notify client to provide valid trace report agent or config server + ErrBlankTraceConfiguration = errors.New("no trace report agent, config server, or collector endpoint specified") +) + +const ( + envJaegerAgentHost = "JAEGER_AGENT_HOST" + envJaegerTags = "JAEGER_TAGS" + envJaegerSamplerManagerHostPort = "JAEGER_SAMPLER_MANAGER_HOST_PORT" + envJaegerSamplerParam = "JAEGER_SAMPLER_PARAM" + envJaegerEndpoint = "JAEGER_ENDPOINT" + envJaegerAgentPort = "JAEGER_AGENT_PORT" + envJaegerSamplerType = "JAEGER_SAMPLER_TYPE" + envJaegerSamplingEndpoint = "JAEGER_SAMPLING_ENDPOINT" + envJaegerDefaultSamplingServerPort = 5778 + envJaegerDefaultUDPSpanServerHost = "localhost" + envJaegerDefaultUDPSpanServerPort = "6831" +) + +// NewFromEnv is a convenience function to allow tracing configuration +// via environment variables +// +// Tracing will be enabled if one (or more) of the following environment variables is used to configure trace reporting: +// - JAEGER_AGENT_HOST +// - JAEGER_SAMPLER_MANAGER_HOST_PORT +func NewOtelFromEnv(serviceName string) (io.Closer, error) { + cfg, err := parseTracingConfig() + if err != nil { + return nil, errors.Wrap(err, "could not load jaeger tracer configuration") + } + if cfg.samplingServerURL == "" && cfg.agentHostPort == "" && cfg.collectorEndpoint == "" { + return nil, ErrBlankTraceConfiguration + } + return cfg.initJaegerTracerProvider(serviceName) +} + +// parseJaegerTags Parse Jaeger tags from env var JAEGER_TAGS, example of TAGs format: key1=value1,key2=${value2:value3} where value2 is an env var +// and value3 is the default value, which is optional. +func parseJaegerTags(sTags string) ([]attribute.KeyValue, error) { + pairs := strings.Split(sTags, ",") + res := make([]attribute.KeyValue, 0, len(pairs)) + for _, p := range pairs { + k, v, found := strings.Cut(p, "=") + if found { + k, v := strings.TrimSpace(k), strings.TrimSpace(v) + if strings.HasPrefix(v, "${") && strings.HasSuffix(v, "}") { + e, d, _ := strings.Cut(v[2:len(v)-1], ":") + v = os.Getenv(e) + if v == "" && d != "" { + v = d + } + } + if v == "" { + return nil, errors.Errorf("invalid tag %q, expected key=value", p) + } + res = append(res, attribute.String(k, v)) + } else if p != "" { + return nil, errors.Errorf("invalid tag %q, expected key=value", p) + } + } + return res, nil +} + +type config struct { + agentHost string + collectorEndpoint string + agentPort string + samplerType string + samplingServerURL string + samplerParam float64 + jaegerTags []attribute.KeyValue + agentHostPort string +} + +// parseTracingConfig facilitates initialization that is compatible with Jaeger's InitGlobalTracer method. +func parseTracingConfig() (config, error) { + cfg := config{} + var err error + + // Parse reporting agent configuration + if e := os.Getenv(envJaegerEndpoint); e != "" { + u, err := url.ParseRequestURI(e) + if err != nil { + return cfg, errors.Wrapf(err, "cannot parse env var %s=%s", envJaegerEndpoint, e) + } + cfg.collectorEndpoint = u.String() + } else { + useEnv := false + host := envJaegerDefaultUDPSpanServerHost + if e := os.Getenv(envJaegerAgentHost); e != "" { + host = e + useEnv = true + } + + port := envJaegerDefaultUDPSpanServerPort + if e := os.Getenv(envJaegerAgentPort); e != "" { + port = e + useEnv = true + } + + if useEnv || cfg.agentHostPort == "" { + cfg.agentHost = host + cfg.agentPort = port + cfg.agentHostPort = net.JoinHostPort(host, port) + } + } + + // Then parse the sampler Configuration + if e := os.Getenv(envJaegerSamplerType); e != "" { + cfg.samplerType = e + } + + if e := os.Getenv(envJaegerSamplerParam); e != "" { + if value, err := strconv.ParseFloat(e, 64); err == nil { + cfg.samplerParam = value + } else { + return cfg, errors.Wrapf(err, "cannot parse env var %s=%s", envJaegerSamplerParam, e) + } + } + + if e := os.Getenv(envJaegerSamplingEndpoint); e != "" { + cfg.samplingServerURL = e + } else if e := os.Getenv(envJaegerSamplerManagerHostPort); e != "" { + cfg.samplingServerURL = e + } else if e := os.Getenv(envJaegerAgentHost); e != "" { + // Fallback if we know the agent host - try the sampling endpoint there + cfg.samplingServerURL = fmt.Sprintf("http://%s:%d/sampling", e, envJaegerDefaultSamplingServerPort) + } + + // When sampling server URL is set, we use the remote sampler + if cfg.samplingServerURL != "" && cfg.samplerType == "" { + cfg.samplerType = "remote" + } + + // Parse tags + cfg.jaegerTags, err = parseJaegerTags(os.Getenv(envJaegerTags)) + if err != nil { + return cfg, errors.Wrapf(err, "could not parse %s", envJaegerTags) + } + return cfg, nil +} + +// initJaegerTracerProvider initializes a new Jaeger Tracer Provider. +func (cfg config) initJaegerTracerProvider(serviceName string) (io.Closer, error) { + // Read environment variables to configure Jaeger + var ep jaegerotel.EndpointOption + // Create the jaeger exporter: address can be either agent address (host:port) or collector Endpoint. + if cfg.agentHostPort != "" { + ep = jaegerotel.WithAgentEndpoint( + jaegerotel.WithAgentHost(cfg.agentHost), + jaegerotel.WithAgentPort(cfg.agentPort)) + } else { + ep = jaegerotel.WithCollectorEndpoint( + jaegerotel.WithEndpoint(cfg.collectorEndpoint)) + } + exp, err := jaegerotel.New(ep) + + if err != nil { + return nil, err + } + + // Configure sampling strategy + sampler := tracesdk.AlwaysSample() + if cfg.samplerType == "const" { + if cfg.samplerParam == 0 { + sampler = tracesdk.NeverSample() + } + } else if cfg.samplerType == "probabilistic" { + tracesdk.TraceIDRatioBased(cfg.samplerParam) + } else if cfg.samplerType == "remote" { + sampler = jaegerremote.New(serviceName, jaegerremote.WithSamplingServerURL(cfg.samplingServerURL), + jaegerremote.WithInitialSampler(tracesdk.TraceIDRatioBased(cfg.samplerParam))) + } else if cfg.samplerType != "" { + return nil, errors.Errorf("unknown sampler type %q", cfg.samplerType) + } + customAttrs := cfg.jaegerTags + customAttrs = append(customAttrs, + attribute.String("samplerType", cfg.samplerType), + attribute.Float64("samplerParam", cfg.samplerParam), + attribute.String("samplingServerURL", cfg.samplingServerURL), + ) + res, err := NewResource(serviceName, customAttrs) + if err != nil { + return nil, err + } + + tp := tracesdk.NewTracerProvider( + tracesdk.WithBatcher(exp), + tracesdk.WithResource(res), + tracesdk.WithSampler(sampler), + ) + + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator([]propagation.TextMapPropagator{ + // w3c Propagator is the default propagator for opentelemetry + propagation.TraceContext{}, propagation.Baggage{}, + // jaeger Propagator is for opentracing backwards compatibility + jaegerpropagator.Jaeger{}, + }...)) + return &Closer{tp}, nil +} + +type Closer struct { + *tracesdk.TracerProvider +} + +func (c Closer) Close() error { + return c.Shutdown(context.Background()) +} + +// ExtractTraceID extracts the trace id, if any from the context. +func ExtractOtelTraceID(ctx context.Context) (string, bool) { + traceID, _ := ExtractOtelSampledTraceID(ctx) + return traceID, traceID != "" +} + +// ExtractOtelSampledTraceID works like ExtractTraceID but the returned bool is only +// true if the returned trace id is sampled. +func ExtractOtelSampledTraceID(ctx context.Context) (string, bool) { + // the case opentelemetry is used with or without bridge + otelSp := trace.SpanFromContext(ctx) + traceID, sampled := otelSp.SpanContext().TraceID(), otelSp.SpanContext().IsSampled() + if traceID.IsValid() { // when noop span is used, the traceID is not valid + return traceID.String(), sampled + } + + return "", false +} diff --git a/tracing/oteltracing_test.go b/tracing/oteltracing_test.go new file mode 100644 index 000000000..5700801a2 --- /dev/null +++ b/tracing/oteltracing_test.go @@ -0,0 +1,136 @@ +package tracing + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" +) + +func TestParseAttributes(t *testing.T) { + os.Setenv("EXISTENT_ENV_KEY", "env_value") + defer os.Unsetenv("EXISTENT_ENV_KEY") + t.Run("ValidAttributes", func(t *testing.T) { + tests := []struct { + input string + expectedOutput []attribute.KeyValue + expectedError error + }{ + { + input: "key1=value1,key2=value2", + expectedOutput: []attribute.KeyValue{ + attribute.String("key1", "value1"), + attribute.String("key2", "value2"), + }, + expectedError: nil, + }, + { + input: "key1=${EXISTENT_ENV_KEY},key2=${NON_EXISTENT_ENV_KEY:default_value}", + expectedOutput: []attribute.KeyValue{ + attribute.String("key1", os.Getenv("EXISTENT_ENV_KEY")), + attribute.String("key2", "default_value"), + }, + expectedError: nil, + }, + } + + for _, test := range tests { + output, err := parseJaegerTags(test.input) + assert.Equal(t, test.expectedOutput, output) + assert.Equal(t, test.expectedError, err) + } + }) + + t.Run("InvalidAttributes", func(t *testing.T) { + tests := []struct { + input string + expectedError string + }{ + { + input: "key1=value1,key2", + expectedError: fmt.Sprintf("invalid tag \"%s\", expected key=value", "key2"), + }, + { + input: "key1=value1,key2=", + expectedError: fmt.Sprintf("invalid tag \"%s\", expected key=value", "key2="), + }, + } + + for _, test := range tests { + _, err := parseJaegerTags(test.input) + assert.Error(t, err, test.expectedError) + } + }) +} + +func TestExtractSampledTraceID(t *testing.T) { + cases := []struct { + desc string + ctx func(*testing.T) (context.Context, func()) + empty bool + }{ + { + desc: "OpenTelemetry", + ctx: getContextWithOpenTelemetry, + }, + { + desc: "No tracer", + ctx: func(_ *testing.T) (context.Context, func()) { + return context.Background(), func() {} + }, + empty: true, + }, + { + desc: "OpenTelemetry with noop", + ctx: getContextWithOpenTelemetryNoop, + empty: true, + }, + } + for _, tc := range cases { + t.Run(tc.desc, func(t *testing.T) { + ctx, closer := tc.ctx(t) + defer closer() + sampledTraceID, sampled := ExtractOtelSampledTraceID(ctx) + traceID, ok := ExtractOtelTraceID(ctx) + + assert.Equal(t, sampledTraceID, traceID, "Expected sampledTraceID to equal traceID") + if tc.empty { + assert.Empty(t, traceID, "Expected traceID to be empty") + assert.False(t, sampled, "Expected sampled to be false") + assert.False(t, ok, "Expected ok to be false") + } else { + assert.NotEmpty(t, traceID, "Expected traceID to be non-empty") + assert.True(t, sampled, "Expected sampled to be true") + assert.True(t, ok, "Expected ok to be true") + } + }) + } +} + +func getContextWithOpenTelemetry(_ *testing.T) (context.Context, func()) { + originTracerProvider := otel.GetTracerProvider() + tp := sdktrace.NewTracerProvider() + otel.SetTracerProvider(tp) + tr := tp.Tracer("test") + ctx, sp := tr.Start(context.Background(), "test") + return ctx, func() { + sp.End() + otel.SetTracerProvider(originTracerProvider) + } +} + +func getContextWithOpenTelemetryNoop(t *testing.T) (context.Context, func()) { + ctx, sp := trace.NewNoopTracerProvider().Tracer("test").Start(context.Background(), "test") + // sanity check + require.False(t, sp.SpanContext().TraceID().IsValid()) + return ctx, func() { + sp.End() + } +} diff --git a/tracing/resource.go b/tracing/resource.go new file mode 100644 index 000000000..9da8b6bb4 --- /dev/null +++ b/tracing/resource.go @@ -0,0 +1,24 @@ +package tracing + +import ( + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" +) + +// NewResource creates a new OpenTelemetry resource using the provided service name and custom attributes. +// This resource will be used for creating both tracers and meters, enriching telemetry data with context. +func NewResource(serviceName string, customAttributes []attribute.KeyValue) (*resource.Resource, error) { + // Append the service name as an attribute to the custom attributes list. + customAttributes = append(customAttributes, semconv.ServiceName(serviceName)) + + // Merge the default resource with the new resource containing custom attributes. + // This ensures that standard attributes are retained while adding custom ones. + return resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + customAttributes..., + ), + ) +} diff --git a/tracing/tracing.go b/tracing/tracing.go deleted file mode 100644 index 841e9a8b9..000000000 --- a/tracing/tracing.go +++ /dev/null @@ -1,84 +0,0 @@ -// Provenance-includes-location: https://github.com/weaveworks/common/blob/main/tracing/tracing.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: Weaveworks Ltd. - -package tracing - -import ( - "context" - "io" - - "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" - jaeger "github.com/uber/jaeger-client-go" - jaegercfg "github.com/uber/jaeger-client-go/config" - jaegerprom "github.com/uber/jaeger-lib/metrics/prometheus" - "go.opentelemetry.io/otel/trace" -) - -// ErrInvalidConfiguration is an error to notify client to provide valid trace report agent or config server -var ( - ErrBlankTraceConfiguration = errors.New("no trace report agent, config server, or collector endpoint specified") -) - -// installJaeger registers Jaeger as the OpenTracing implementation. -func installJaeger(serviceName string, cfg *jaegercfg.Configuration, options ...jaegercfg.Option) (io.Closer, error) { - metricsFactory := jaegerprom.New() - - // put the metricsFactory earlier so provided options can override it - opts := append([]jaegercfg.Option{jaegercfg.Metrics(metricsFactory)}, options...) - - closer, err := cfg.InitGlobalTracer(serviceName, opts...) - if err != nil { - return nil, errors.Wrap(err, "could not initialize jaeger tracer") - } - return closer, nil -} - -// NewFromEnv is a convenience function to allow tracing configuration -// via environment variables -// -// Tracing will be enabled if one (or more) of the following environment variables is used to configure trace reporting: -// - JAEGER_AGENT_HOST -// - JAEGER_SAMPLER_MANAGER_HOST_PORT -func NewFromEnv(serviceName string, options ...jaegercfg.Option) (io.Closer, error) { - cfg, err := jaegercfg.FromEnv() - if err != nil { - return nil, errors.Wrap(err, "could not load jaeger tracer configuration") - } - - if cfg.Sampler.SamplingServerURL == "" && cfg.Reporter.LocalAgentHostPort == "" && cfg.Reporter.CollectorEndpoint == "" { - return nil, ErrBlankTraceConfiguration - } - - return installJaeger(serviceName, cfg, options...) -} - -// ExtractTraceID extracts the trace id, if any from the context. -func ExtractTraceID(ctx context.Context) (string, bool) { - sp := trace.SpanFromContext(ctx) - if sp == nil { - return "", false - } - sctx, ok := sp.Context().(jaeger.SpanContext) - if !ok { - return "", false - } - - return sctx.TraceID().String(), true -} - -// ExtractSampledTraceID works like ExtractTraceID but the returned bool is only -// true if the returned trace id is sampled. -func ExtractSampledTraceID(ctx context.Context) (string, bool) { - sp := trace.SpanFromContext(ctx) - if sp == nil { - return "", false - } - sctx, ok := sp.Context().(jaeger.SpanContext) - if !ok { - return "", false - } - - return sctx.TraceID().String(), sctx.IsSampled() -}