From efbef38deddd7860be5727e1687c7cb0624713a2 Mon Sep 17 00:00:00 2001 From: Olivier Campeau Date: Thu, 4 Sep 2025 14:39:40 -0400 Subject: [PATCH 1/2] tracing: Introduce OpenTelemetry tracer configuration This commit adds the necessary code to initialize a tracer provider within Gitaly itself. Doing so, it removes the need for depending on the LabKit library. For backward compatibility, it uses the same connection-string scheme to pass along configuration. Support for both HTTP and gRPC is included. Reference: https://gitlab.com/gitlab-org/gitaly/-/issues/4329 --- go.mod | 134 +++++++----- go.sum | 289 ++++++++++++++----------- internal/tracing/configuration.go | 226 +++++++++++++++++++ internal/tracing/configuration_test.go | 137 ++++++++++++ internal/tracing/middlewares.go | 17 ++ internal/tracing/passthrough.go | 24 +- internal/tracing/tracer.go | 173 +++++++++++++++ internal/tracing/tracer_test.go | 80 +++++++ 8 files changed, 889 insertions(+), 191 deletions(-) create mode 100644 internal/tracing/configuration.go create mode 100644 internal/tracing/configuration_test.go create mode 100644 internal/tracing/middlewares.go create mode 100644 internal/tracing/tracer.go create mode 100644 internal/tracing/tracer_test.go diff --git a/go.mod b/go.mod index ff74ff321b9..084075b245c 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,9 @@ go 1.24.0 replace github.com/go-enry/go-license-detector/v4 => github.com/gl-gitaly/go-license-detector/v4 v4.0.0-20230524080836-4cc9a3796917 require ( - cloud.google.com/go/storage v1.44.0 + cloud.google.com/go/storage v1.50.0 github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.2 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 github.com/ProtonMail/go-crypto v1.1.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.63.1 github.com/cloudflare/tableflip v1.2.3 @@ -43,17 +44,28 @@ require ( github.com/urfave/cli/v3 v3.3.3 gitlab.com/gitlab-org/labkit v1.31.2 go.etcd.io/raft/v3 v3.6.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 + go.opentelemetry.io/contrib/propagators/ot v1.38.0 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 go.uber.org/automaxprocs v1.6.0 go.uber.org/goleak v1.3.0 gocloud.dev v0.40.1-0.20241107185025-56954848c3aa golang.org/x/crypto v0.42.0 - golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 golang.org/x/net v0.44.0 // indirect golang.org/x/sync v0.17.0 golang.org/x/sys v0.37.0 golang.org/x/text v0.30.0 golang.org/x/time v0.11.0 - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c + // +gitaly pinVersion google.golang.org/grpc v1.71.1 // Please upgrade grpc-go with caution. Newer grpc-go versions contain some known issues: // - https://gitlab.com/gitlab-com/request-for-help/-/issues/2127 @@ -63,15 +75,15 @@ require ( ) require ( - cel.dev/expr v0.23.1 // indirect - cloud.google.com/go v0.115.1 // indirect - cloud.google.com/go/auth v0.9.3 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/iam v1.2.1 // indirect - cloud.google.com/go/monitoring v1.21.0 // indirect + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.120.0 // indirect + cloud.google.com/go/auth v0.16.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/iam v1.5.2 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/profiler v0.1.0 // indirect - cloud.google.com/go/trace v1.11.0 // indirect + cloud.google.com/go/trace v1.11.6 // indirect contrib.go.opencensus.io/exporter/stackdriver v0.13.14 // indirect dario.cat/mergo v1.0.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.1 // indirect @@ -81,39 +93,40 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect github.com/DataDog/datadog-go v4.4.0+incompatible // indirect - github.com/DataDog/sketches-go v1.0.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect + github.com/DataDog/sketches-go v1.4.7 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect - github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 // indirect github.com/avast/retry-go v3.0.0+incompatible // indirect - github.com/aws/aws-sdk-go v1.55.5 // indirect - github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect + github.com/aws/aws-sdk-go v1.55.6 // indirect + github.com/aws/aws-sdk-go-v2 v1.37.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 // indirect + github.com/aws/aws-sdk-go-v2/config v1.30.1 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect - github.com/aws/smithy-go v1.21.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 // indirect + github.com/aws/smithy-go v1.22.5 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cilium/ebpf v0.16.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect - github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/containerd/log v0.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cyphar/filepath-securejoin v0.2.5 // indirect @@ -137,19 +150,21 @@ require ( github.com/go-git/go-billy/v5 v5.6.0 // indirect github.com/go-git/go-git/v5 v5.13.0 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/golang/mock v1.7.0-rc.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/flatbuffers v25.2.10+incompatible // indirect - github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da // indirect - github.com/google/s2a-go v0.1.8 // indirect + github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a // indirect + github.com/google/s2a-go v0.1.9 // indirect github.com/google/wire v0.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect - github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hhatto/gorst v0.0.0-20181029133204-ca9f730cac5b // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -177,55 +192,56 @@ require ( github.com/moby/sys/userns v0.1.0 // indirect github.com/montanaflynn/stats v0.7.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oklog/ulid/v2 v2.0.2 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect github.com/olekukonko/ll v0.0.8 // indirect github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect - github.com/philhofer/fwd v1.1.1 // indirect + github.com/onsi/gomega v1.35.1 // indirect + github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/prometheus/prometheus v0.54.0 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/prometheus/prometheus v0.303.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/shirou/gopsutil/v3 v3.21.12 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shogo82148/go-shuffle v1.0.1 // indirect github.com/skeema/knownhosts v1.3.0 // indirect github.com/ssgelm/cookiejarparser v1.0.1 // indirect - github.com/tinylib/msgp v1.1.2 // indirect - github.com/tklauser/go-sysconf v0.3.9 // indirect - github.com/tklauser/numcpus v0.3.0 // indirect + github.com/tinylib/msgp v1.3.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - github.com/yusufpapurcu/wmi v1.2.2 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect gitlab.com/gitlab-org/go/reopen v1.0.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.28.0 // indirect golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/tools v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect - gonum.org/v1/gonum v0.11.0 // indirect - google.golang.org/api v0.197.0 // indirect - google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + gonum.org/v1/gonum v0.16.0 // indirect + google.golang.org/api v0.229.0 // indirect + google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect gopkg.in/DataDog/dd-trace-go.v1 v1.32.0 // indirect gopkg.in/neurosnap/sentences.v1 v1.0.7 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index c6ea71dbae6..e4870b5df98 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= -cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= 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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -25,30 +25,30 @@ cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSU cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.92.2/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.115.1 h1:Jo0SM9cQnSkYfp44+v+NQXHpcHqlnRJk2qxh6yvxxxQ= -cloud.google.com/go v0.115.1/go.mod h1:DuujITeaufu3gL68/lOFIirVNJwQeyf5UXyi+Wbgknc= -cloud.google.com/go/auth v0.9.3 h1:VOEUIAADkkLtyfr3BLa3R8Ed/j6w1jTBmARx+wb5w5U= -cloud.google.com/go/auth v0.9.3/go.mod h1:7z6VY+7h3KUdRov5F1i8NDP5ZzWKYmEPO842BgCsmTk= -cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= -cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= +cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= +cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= +cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.2.1 h1:QFct02HRb7H12J/3utj0qf5tobFh9V4vR6h9eX5EBRU= -cloud.google.com/go/iam v1.2.1/go.mod h1:3VUIJDPpwT6p/amXRC5GY8fCCh70lxPygguVtI0Z4/g= -cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= -cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= -cloud.google.com/go/longrunning v0.6.1 h1:lOLTFxYpr8hcRtcwWir5ITh1PAKUD/sG2lKrTSYjyMc= -cloud.google.com/go/longrunning v0.6.1/go.mod h1:nHISoOZpBcmlwbJmiVk5oDRz0qG/ZxPynEGs1iZ79s0= -cloud.google.com/go/monitoring v1.21.0 h1:EMc0tB+d3lUewT2NzKC/hr8cSR9WsUieVywzIHetGro= -cloud.google.com/go/monitoring v1.21.0/go.mod h1:tuJ+KNDdJbetSsbSGTqnaBvbauS5kr3Q/koy3Up6r+4= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw= +cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= cloud.google.com/go/profiler v0.1.0 h1:MG/rxKC1MztRfEWMGYKFISxyZak5hNh29f0A/z2tvWk= cloud.google.com/go/profiler v0.1.0/go.mod h1:D7S7LV/zKbRWkOzYL1b5xytpqt8Ikd/v/yvf1/Tx2pQ= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= @@ -60,10 +60,10 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.44.0 h1:abBzXf4UJKMmQ04xxJf9dYM/fNl24KHoTuBjyJDX2AI= -cloud.google.com/go/storage v1.44.0/go.mod h1:wpPblkIuMP5jCB/E48Pz9zIo2S/zD8g+ITmxKkPCITE= -cloud.google.com/go/trace v1.11.0 h1:UHX6cOJm45Zw/KIbqHe4kII8PupLt/V5tscZUkeiJVI= -cloud.google.com/go/trace v1.11.0/go.mod h1:Aiemdi52635dBR7o3zuc9lLjXo3BwGaChEjCa3tJNmM= +cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= +cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= +cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= +cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= @@ -96,21 +96,24 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/datadog-go v4.4.0+incompatible h1:R7WqXWP4fIOAqWJtUKmSfuc7eDsBT58k9AY5WSHVosk= github.com/DataDog/datadog-go v4.4.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/gostackparse v0.5.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM= -github.com/DataDog/sketches-go v1.0.0 h1:chm5KSXO7kO+ywGWJ0Zs6tdmWU8PBXSbywFVciL6BG4= github.com/DataDog/sketches-go v1.0.0/go.mod h1:O+XkJHWk9w4hDwY2ZUDU31ZC9sNYlYo8DiFsxjYeo1k= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/DataDog/sketches-go v1.4.7 h1:eHs5/0i2Sdf20Zkj0udVFWuCrXGRFig2Dcfm5rtcTxc= +github.com/DataDog/sketches-go v1.4.7/go.mod h1:eAmQ/EBmtSO+nQp7IZMZVRPT4BQTmIc5RZQ+deGlTPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 h1:5IT7xOdq17MtcdtL/vtl6mGfzhaq4m4vpollPRmlsBQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0/go.mod h1:ZV4VOm0/eHR06JLrXWe09068dHpr3TRpY9Uo7T+anuA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 h1:Jtr816GUk6+I2ox9L/v+VcOwN6IyGOEDTSNHfD6m9sY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0/go.mod h1:E05RN++yLx9W4fXPtX978OLo9P0+fBacauUdET1BckA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= @@ -124,50 +127,52 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= -github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= -github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= -github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= +github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= +github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.37.0 h1:YtCOESR/pN4j5oA7cVHSfOwIcuh/KwHC4DOSXFbv5F0= +github.com/aws/aws-sdk-go-v2 v1.37.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 h1:xDAuZTn4IMm8o1LnBZvmrL8JA1io4o3YWNXgohbf20g= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5/go.mod h1:wYSv6iDS621sEFLfKvpPE2ugjTuGlAG7iROg0hLOkfc= -github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= -github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/config v1.30.1 h1:sHL8g/+9tcZATeV2tEkEfxZeaNokDtKsSjGMGHD49qA= +github.com/aws/aws-sdk-go-v2/config v1.30.1/go.mod h1:wkibEyFfxXRyTSzRU4bbF5IUsSXyE4xQ4ZjkGmi5tFo= +github.com/aws/aws-sdk-go-v2/credentials v1.18.1 h1:E55xvOqlX7CvB66Z7rSM9usCrFU1ryUIUHqiXsEzVoE= +github.com/aws/aws-sdk-go-v2/credentials v1.18.1/go.mod h1:iobSQfR5MkvILxssGOvi/P1jjOhrRzfTiCPCzku0vx4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0 h1:9sBTeKQwAvmJUWKIACIoiFSnxxl+sS++YDfr17/ngq0= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.0/go.mod h1:LW9/PxQD1SYFC7pnWcgqPhoyZprhjEdg5hBK6qYPLW8= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 h1:kYQ3H1u0ANr9KEKlGs/jTLrBFPo8P8NaH/w7A01NeeM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18/go.mod h1:r506HmK5JDUh9+Mw4CfGJGSSoqIiLCndAuqXuhbv67Y= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18 h1:Z7IdFUONvTcvS7YuhtVxN99v2cCoHRXOS4mTr0B/pUc= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.18/go.mod h1:DkKMmksZVVyat+Y+r1dEOgJEfUeA7UngIHWeKsi0yNc= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 h1:H2iZoqW/v2Jnrh1FnU725Bq6KJ0k2uP63yH+DcY+HUI= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0/go.mod h1:L0FqLbwMXHvNC/7crWV1iIxUlOKYZUE8KuTIA+TozAI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 h1:EDped/rNzAhFPhVY0sDGbtD16OKqksfA8OjF/kLEgw8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0/go.mod h1:uUI335jvzpZRPpjYx6ODc/wg1qH+NnoSTK/FwVeK0C0= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18 h1:OWYvKL53l1rbsUmW7bQyJVsYU/Ii3bbAAQIIFNbM0Tk= github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.18/go.mod h1:CUx0G1v3wG6l01tUB+j7Y8kclA8NSqK4ef0YG79a4cg= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 h1:QFASJGfT8wMXtuP3D5CRmMjARHv9ZmzFUMJznHDOY3w= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5/go.mod h1:QdZ3OmoIjSX+8D1OPAzPxDfjXASbBMDsz9qvtyIhtik= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20 h1:rTWjG6AvWekO2B1LHeM3ktU7MqyX9rzWQ7hgzneZW7E= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.20/go.mod h1:RGW2DDpVc8hu6Y6yG8G5CHVmVOAn1oV8rNKOHRJyswg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 h1:Xbwbmk44URTiHNx6PNo0ujDE6ERlsCKJD3u1zfnzAPg= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20/go.mod h1:oAfOFzUB14ltPZj1rWwRc3d/6OgD76R8KlvU3EqM9Fg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 h1:eRhU3Sh8dGbaniI6B+I48XJMrTPRkK4DKo+vqIxziOU= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0/go.mod h1:paNLV18DZ6FnWE/bd06RIKPDIFpjuvCkGKWTG/GDBeM= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18 h1:eb+tFOIl9ZsUe2259/BKPeniKuz4/02zZFH/i4Nf8Rg= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.18/go.mod h1:GVCC2IJNJTmdlyEsSmofEy7EfJncP7DNnXDzRjJ5Keg= github.com/aws/aws-sdk-go-v2/service/s3 v1.63.1 h1:TR96r56VwELV0qguNFCuz+/bEpRfnR3ZsS9/IG05C7Q= github.com/aws/aws-sdk-go-v2/service/s3 v1.63.1/go.mod h1:NLTqRLe3pUNu3nTEHI6XlHLKYmc8fbHUdMxAB6+s41Q= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= -github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= -github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= -github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= -github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.0 h1:cuFWHH87GP1NBGXXfMicUbE7Oty5KpPxN6w4JpmuxYc= +github.com/aws/aws-sdk-go-v2/service/sso v1.26.0/go.mod h1:aJBemdlbCKyOXEXdXBqS7E+8S9XTDcOTaoOjtng54hA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0 h1:t2va+wewPOYIqC6XyJ4MGjiGKkczMAPsgq5W4FtL9ME= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.31.0/go.mod h1:ExCTcqYqN0hYYRsDlBVU8+68grqlWdgX9/nZJwQW4aY= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.0 h1:FD9agdG4CeOGS3ORLByJk56YIXDS7mxFpmZyCtpqExc= +github.com/aws/aws-sdk-go-v2/service/sts v1.35.0/go.mod h1:NDzDPbBF1xtSTZUMuZx0w3hIfWzcL7X2AQ0Tr9becIQ= +github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= +github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= @@ -188,8 +193,8 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= @@ -292,13 +297,14 @@ github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpj github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/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= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= @@ -326,8 +332,9 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -395,23 +402,23 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210804190019-f964ff605595/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da h1:xRmpO92tb8y+Z85iUOMOicpCfaYcv7o3Cg3wKrIpg8g= -github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a h1://KbezygeMJZCSHH+HgUZiTeSoiuFspbMg1ge+eFj18= +github.com/google/pprof v0.0.0-20250607225305-033d6d78b36a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/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/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= -github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= -github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= -github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= @@ -423,6 +430,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 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-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -528,8 +537,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/neurosnap/sentences v1.0.6 h1:iBVUivNtlwGkYsJblWV8GGVFmXzZzak907Ci8aA0VTE= github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/oklog/ulid/v2 v2.0.2 h1:r4fFzBm+bv0wNKNh5eXTwU7i85y5x+uwkxCUTNVQqLc= -github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo= github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc= @@ -543,8 +552,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY= github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -554,8 +563,8 @@ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYr github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ= -github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= +github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= @@ -570,8 +579,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY= github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= @@ -583,18 +592,18 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/prometheus/prometheus v0.54.0 h1:6+VmEkohHcofl3W5LyRlhw1Lfm575w/aX6ZFyVAmzM0= -github.com/prometheus/prometheus v0.54.0/go.mod h1:xlLByHhk2g3ycakQGrMaU8K7OySZx98BzeCR99991NY= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY= +github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck= github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= 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/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rubenv/sql-migrate v1.8.0 h1:dXnYiJk9k3wetp7GfQbKJcPHjVJL6YK19tKj8t2Ns0o= github.com/rubenv/sql-migrate v1.8.0/go.mod h1:F2bGFBwCU+pnmbtNYDeKvSuvL6lBVtXDXUUv5t+u1qw= github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 h1:mncRSDOqYCng7jOD+Y6+IivdRI6Kzv2BLWYkWkdQfu0= @@ -606,8 +615,12 @@ github.com/sebest/xff v0.0.0-20210106013422-671bd2870b3a/go.mod h1:wozgYq9WEBQBa github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/shirou/gopsutil/v3 v3.21.2/go.mod h1:ghfMypLDrFSWN2c9cDYFLHyynQ+QUht0cv/18ZqVczw= -github.com/shirou/gopsutil/v3 v3.21.12 h1:VoGxEW2hpmz0Vt3wUvHIl9fquzYLNpVpgNNB7pGJimA= -github.com/shirou/gopsutil/v3 v3.21.12/go.mod h1:BToYZVTlSVlfazpDDYFnsVZLaoRG+g8ufT6fPQLdJzA= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc= github.com/shogo82148/go-shuffle v1.0.1 h1:4swIpHXLMAz14DE4YTgakgadpRN0n1wE1dieGnOTVFU= github.com/shogo82148/go-shuffle v1.0.1/go.mod h1:HQPjVgUUZ9TNgm4/K/iXRuAdhPsQrXnAGgtk/9kqbBY= @@ -636,14 +649,15 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= +github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek= -github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo= -github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8= -github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= -github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= @@ -663,9 +677,10 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/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= +github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= -github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= gitlab.com/gitlab-org/go/reopen v1.0.0 h1:6BujZ0lkkjGIejTUJdNO1w56mN1SI10qcVQyQlOPM+8= gitlab.com/gitlab-org/go/reopen v1.0.0/go.mod h1:D6OID8YJDzEVZNYW02R/Pkj0v8gYFSIhXFTArAsBQw8= gitlab.com/gitlab-org/labkit v1.31.2 h1:uot1m2vh7DT+JCq2LFUYIP6hk0qw9Ebw2eZKLk8BYqI= @@ -683,23 +698,37 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= +go.opentelemetry.io/contrib/propagators/ot v1.38.0 h1:k4gSyyohaDXI8F9BDXYC3uO2vr5sRNeQFMsN9Zn0EoI= +go.opentelemetry.io/contrib/propagators/ot v1.38.0/go.mod h1:2hDsuiHRO39SRUMhYGqmj64z/IuMRoxE4bBSFR82Lo8= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= @@ -709,6 +738,8 @@ go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= @@ -741,8 +772,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo= -golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -769,6 +800,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -812,6 +844,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= @@ -906,8 +939,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1012,6 +1044,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= @@ -1026,8 +1059,8 @@ golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUO golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= -gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= -gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1056,8 +1089,8 @@ google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtuk google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.197.0 h1:x6CwqQLsFiA5JKAiGyGBjc2bNtHtLddhJCE2IKuhhcQ= -google.golang.org/api v0.197.0/go.mod h1:AuOuo20GoQ331nq7DquGHlU6d+2wN2fZ8O0ta60nRNw= +google.golang.org/api v0.229.0 h1:p98ymMtqeJ5i3lIBMj5MpR9kzIIgzpHHh8vQ+vgAzx8= +google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1117,12 +1150,12 @@ google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= -google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:hL97c3SYopEHblzpxRL4lSs523++l8DYxGM1FQiYmb4= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= diff --git a/internal/tracing/configuration.go b/internal/tracing/configuration.go new file mode 100644 index 00000000000..d3b3f8f05eb --- /dev/null +++ b/internal/tracing/configuration.go @@ -0,0 +1,226 @@ +package tracing + +import ( + "errors" + "fmt" + "net" + "net/url" + "strconv" + "strings" +) + +const ( + // gitlabTracingEnvKey is the environment variable that contains the + // tracing configuration in the form of a connection string. + // The value of that variable is in one of those two format: + // 1. opentracing://?option1=value1?option2=value2 + // 2. http://host:port?option1=value1?option2=value2 + // This format is a legacy of the LabKit library used previously. + // The original parsing logic can be found here: + // https://gitlab.com/gitlab-org/labkit/-/blob/v1.31.2/tracing/connstr/connection_string_parser.go?ref_type=tags#L17 + gitlabTracingEnvKey = "GITLAB_TRACING" + + // optHeaderPrefix is the prefix used to provide HTTP headers into the + // GITLAB_TRACING connection string. + // When using OpenTelemetry, authentication to tracing backends is done by + // providing tokens or API keys through HTTP headers. + // Example: opentracing://datadog?option1=value1?header^DD_API_KEY=apikey + // In the example above, DD_API_KEY=apikey will be parsed as a header and + // included in each request to the Datadog backend. + optHeaderPrefix = "header^" + + // optSamplerParameter is the expected key in the connection string to provide the sampler parameter + optSamplerParameter = "sampler_param" + + otelGrpcProtocol = "otlp-grpc" + otelHTTPProtocol = "otlp-http" + + otlpGrpcDefaultEndpoint = "localhost:4317" + otlpHTTPDefaultEndpoint = "localhost:4318" + + vendorStackDriver = "stackdriver" + + defaultSamplerParamValue = 0.001 +) + +var errInvalidConfiguration = errors.New("invalid configuration") + +// tracingConfig holds the configuration extracted from GITLAB_TRACING +type tracingConfig struct { + // protocol to use when collector endpoint is accessible locally: otlp-grpc or otlp-http + protocol string + + // endpoint of the OTEL collector + endpoint string + + // vendor is the vendor that provides the tracing backend. + // This field is set when the collector is managed by a cloud provider such as stackdriver. + // If the collector is running locally or can be accessed by a URL directly + // (without the need of an SDK such as Google Cloud SDK), this field must remain empty + // and `protocol` and `endpoint` must be set. + vendor string + + // insecure relates to TLS/SSL. + // If true, HTTP will be used. + // If false, HTTPs will be used. + // Default is true (HTTP). + insecure bool + + // serviceName is he name of the service being instrumented. + serviceName string + + // samplingParam is a float value indicating the ratio to use for sampling. + // 1.0 = 100% + // 0.0 = 0% + samplingParam float64 + + // headers are included in each request sent to the collector endpoint. + // This is useful to pass along information such as API keys or tokens for authentication. + headers map[string]string +} + +// parseConnectionString parses a connection string and extracts tracing configuration. +// It returns an error if the configuration is invalid. +func parseConnectionString(connStr string) (cfg tracingConfig, err error) { + // Handle empty connection string + if connStr == "" { + return tracingConfig{}, + fmt.Errorf("%w: empty connection string", errInvalidConfiguration) + } + + // Parse URL + u, err := url.Parse(connStr) + if err != nil { + return tracingConfig{}, + fmt.Errorf("%w: failed to parse connection string: %w", errInvalidConfiguration, err) + } + + options := make(map[string]string) + headers := make(map[string]string) + + // Extract options from query parameters + for k, v := range u.Query() { + if len(v) == 0 { + continue + } + // if the query param is a header, add it to the header map + // else it is an option + if h, ok := strings.CutPrefix(k, optHeaderPrefix); ok { + headers[h] = v[0] + } else { + options[k] = v[0] + } + } + + var endpoint string + + // Extract protocol + protocol := u.Scheme + if protocol == "" { + return tracingConfig{}, + fmt.Errorf("%w: no protocol specified in connection string", errInvalidConfiguration) + } + + vendor := "" + switch protocol { + case otelGrpcProtocol, otelHTTPProtocol: + // For OpenTelemetry, the format is: + // otlp-grpc://localhost:4317?opt1=val1&opt2=val2 + // otlp-http://localhost:4318?opt1=val1&opt2=val2 + endpoint = u.Host + if endpoint == "" { + return tracingConfig{}, + fmt.Errorf("%w: no endpoint specified in connection string", errInvalidConfiguration) + } + if _, _, err := net.SplitHostPort(endpoint); err != nil { + return tracingConfig{}, + fmt.Errorf("%w: invalid endpoint format %q: %w", errInvalidConfiguration, endpoint, err) + } + + case "opentracing": + // When `opentracing` is used as the protocol, the format of the connection string is: + // opentracing://?opt1=val1&opt2=val2 + // OpenTracing is the legacy distributed tracing protocol, and is supported here for backward + // compatibility reasons. + // When using `opentracing`, only Stackdriver and Jaeger are supported. + // If the provider is not `jaeger` or `stackdriver`, it defaults to using OpenTelemetry + // protocol on the default gRPC endpoint. + provider := u.Host + if provider == "" { + return tracingConfig{}, + fmt.Errorf("%w: no provider specified in opentracing connection string", errInvalidConfiguration) + } + + // Map OpenTracing providers to OpenTelemetry protocols + switch provider { + case vendorStackDriver: + vendor = provider + protocol = "" + endpoint = "" + default: + // Jaeger is a special case because it is not a vendor + protocol = otelGrpcProtocol + endpoint = otlpGrpcDefaultEndpoint + if udpEndpoint, ok := options["udp_endpoint"]; ok { + // Parse the UDP endpoint to extract host:port + host, _, err := net.SplitHostPort(udpEndpoint) + if err == nil && host != "" { + // Use the host from UDP endpoint but with the OTLP port + endpoint = net.JoinHostPort(host, "4317") + } + + } + } + default: + return tracingConfig{}, + fmt.Errorf("%w: unsupported protocol (%s)", errInvalidConfiguration, protocol) + } + + // Check for insecure options + insecure := true + if val, ok := options["insecure"]; ok { + pval, err := strconv.ParseBool(val) + if err != nil { + return tracingConfig{}, + fmt.Errorf("%w: insecure option must be a boolean value: %w", errInvalidConfiguration, err) + } + insecure = pval + } + + samplerFloatValue := defaultSamplerParamValue + + // Check for sampler value in options + // If the option is not defined, we assume default value + if !strings.EqualFold(options[optSamplerParameter], "") { + f, err := strconv.ParseFloat(options[optSamplerParameter], 64) + if err != nil { + return tracingConfig{}, + fmt.Errorf("%w: %s must be a float value: %w", errInvalidConfiguration, optSamplerParameter, err) + } + + if f > 1.0 || f < 0.0 { + return tracingConfig{}, + fmt.Errorf("%s is '%v' but must be a float value between 0.0 and 1.0", optSamplerParameter, samplerFloatValue) + } + + samplerFloatValue = clampValue(f, 0.0, 1.0) + } + + return tracingConfig{ + protocol: protocol, + endpoint: endpoint, + vendor: vendor, + insecure: insecure, + samplingParam: samplerFloatValue, + headers: headers, + }, nil +} + +func clampValue(val, min, max float64) float64 { + if val > max { + return 1.0 + } else if val < min { + return 0.0 + } + return val +} diff --git a/internal/tracing/configuration_test.go b/internal/tracing/configuration_test.go new file mode 100644 index 00000000000..2756ed5b641 --- /dev/null +++ b/internal/tracing/configuration_test.go @@ -0,0 +1,137 @@ +package tracing + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_parseConnectionString(t *testing.T) { + tests := []struct { + name string + connStr string + wantCfg tracingConfig + wantErr error + }{ + { + name: "otlp-grpc: when host is not specified it should return an error", + connStr: "otlp-grpc://", + wantCfg: tracingConfig{}, + wantErr: errInvalidConfiguration, + }, + { + name: "otlp-http: when host is not specified it should return an error", + connStr: "otlp-http://", + wantCfg: tracingConfig{}, + wantErr: errInvalidConfiguration, + }, + { + name: "otlp-grpc protocol: with headers", + connStr: "otlp-grpc://localhost:1234?header^API-KEY=my-key", + wantCfg: tracingConfig{ + protocol: otelGrpcProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-http protocol: with headers", + connStr: "otlp-http://localhost:1234?header^API-KEY=my-key", + wantCfg: tracingConfig{ + protocol: otelHTTPProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-grpc protocol: with insecure false", + connStr: "otlp-grpc://localhost:1234?header^API-KEY=my-key&insecure=false", + wantCfg: tracingConfig{ + protocol: otelGrpcProtocol, + endpoint: "localhost:1234", + insecure: false, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-http protocol: with insecure false", + connStr: "otlp-http://localhost:1234?header^API-KEY=my-key&insecure=true", + wantCfg: tracingConfig{ + protocol: otelHTTPProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-grpc protocol: with insecure true", + connStr: "otlp-grpc://localhost:1234?header^API-KEY=my-key&insecure=true", + wantCfg: tracingConfig{ + protocol: otelGrpcProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-http protocol: with insecure true", + connStr: "otlp-http://localhost:1234?header^API-KEY=my-key&insecure=true", + wantCfg: tracingConfig{ + protocol: otelHTTPProtocol, + endpoint: "localhost:1234", + insecure: true, + samplingParam: defaultSamplerParamValue, + headers: map[string]string{"API-KEY": "my-key"}, + }, + }, + { + name: "otlp-http protocol: with typo in insecure value", + connStr: "otlp-http://localhost:1234?header^API-KEY=my-key&insecure=flase", + wantCfg: tracingConfig{}, + }, + { + name: "invalid provider: it should return an error", + connStr: "something://", + wantCfg: tracingConfig{}, + wantErr: errInvalidConfiguration, + }, + { + name: "jaeger provider", + connStr: "opentracing://jaeger?http_endpoint=127.0.0.1:1234&sampler_param=0.76", + wantCfg: tracingConfig{ + protocol: otelGrpcProtocol, + endpoint: otlpGrpcDefaultEndpoint, + insecure: true, + samplingParam: 0.76, + headers: map[string]string{}, + }, + }, + { + name: "stackdriver provider", + connStr: "opentracing://stackdriver?http_endpoint=127.0.0.1:1234&sampler_param=1.0", + wantCfg: tracingConfig{ + insecure: true, + vendor: vendorStackDriver, + samplingParam: 1.0, + headers: map[string]string{}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotCfg, err := parseConnectionString(tt.connStr) + if tt.wantErr != nil { + assert.ErrorIs(t, err, tt.wantErr) + return + } + assert.Equalf(t, tt.wantCfg, gotCfg, "parseConnectionString(%v)", tt.connStr) + }) + } +} diff --git a/internal/tracing/middlewares.go b/internal/tracing/middlewares.go new file mode 100644 index 00000000000..a9ee2206489 --- /dev/null +++ b/internal/tracing/middlewares.go @@ -0,0 +1,17 @@ +package tracing + +import ( + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc/stats" +) + +// NewGRPCServerStatsHandler is an OTEL instrumented middleware for gRPC servers. It returns a stats.Handler as +// recommended by the OTEL project after deprecating the UnaryServerInterceptor. +// See: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc@v0.54.0#UnaryServerInterceptor +func NewGRPCServerStatsHandler(opts ...otelgrpc.Option) stats.Handler { + defaultOpts := []otelgrpc.Option{ + otelgrpc.WithPropagators(defaultPropagator), + } + + return otelgrpc.NewServerHandler(append(defaultOpts, opts...)...) +} diff --git a/internal/tracing/passthrough.go b/internal/tracing/passthrough.go index 9cbc61e566c..39af2408afa 100644 --- a/internal/tracing/passthrough.go +++ b/internal/tracing/passthrough.go @@ -42,7 +42,7 @@ func StreamPassthroughInterceptor(spanContext opentracing.SpanContext) grpc.Stre func injectSpanContext(parentCtx context.Context, spanContext opentracing.SpanContext) context.Context { tracer := opentracing.GlobalTracer() md := grpcmwmetadata.ExtractOutgoing(parentCtx).Clone() - if err := tracer.Inject(spanContext, opentracing.HTTPHeaders, metadataTextMap(md)); err != nil { + if err := tracer.Inject(spanContext, opentracing.HTTPHeaders, grpcMetadataCarrier(md)); err != nil { return parentCtx } ctxWithMetadata := md.ToOutgoing(parentCtx) @@ -64,8 +64,24 @@ func environAsMap(env []string) map[string]string { // fields. gRPC header name format is: // > Header-Name → 1*( %x30-39 / %x61-7A / "_" / "-" / ".") ; 0-9 a-z _ - . // > Source: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md -type metadataTextMap metadata.MD +type grpcMetadataCarrier metadata.MD -func (m metadataTextMap) Set(key, val string) { - m[strings.ToLower(key)] = []string{val} +func (g grpcMetadataCarrier) Get(key string) string { + values := metadata.MD(g).Get(key) + if len(values) == 0 { + return "" + } + return values[0] +} + +func (g grpcMetadataCarrier) Set(key string, value string) { + metadata.MD(g).Set(strings.ToLower(key), value) +} + +func (g grpcMetadataCarrier) Keys() []string { + var keys []string + for k := range g { + keys = append(keys, k) + } + return keys } diff --git a/internal/tracing/tracer.go b/internal/tracing/tracer.go new file mode 100644 index 00000000000..b4163d4f4e0 --- /dev/null +++ b/internal/tracing/tracer.go @@ -0,0 +1,173 @@ +package tracing + +import ( + "context" + "errors" + "fmt" + "io" + "os" + + stackdriver "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace" + "go.opentelemetry.io/contrib/propagators/jaeger" + "go.opentelemetry.io/contrib/propagators/ot" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + oteltracingsdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" +) + +var errTracingDisabled = errors.New("tracing is disabled") + +// defaultPropagator is the default TextMapPropagator +// Propagators are objects responsible to propagate tracing data. +// Note: contrary to OpenTracing, OpenTelemetry propagates baggage within contexts, not within spans. +var defaultPropagator = propagation.NewCompositeTextMapPropagator( + // TraceContext propagator propagates trace IDs across process boundaries + propagation.TraceContext{}, + + // Baggage propagator propagates baggage across process boundaries. + propagation.Baggage{}, + + // OpenTracing propagator is needed because other components at GitLab still use it. + ot.OT{}, + + // Jaeger/Uber propagator is needed because other components at GitLab still use it. + jaeger.Jaeger{}, +) + +// InitializeTracerProvider creates an OpenTelemetry tracer provider. +// The configuration is coming from the GITLAB_TRACING environment variable +// in the form of a connection string. +// It has two format: +// 1. opentracing://jaeger?host=localhost:1234&opt1=val1&opt2=val2 +// 2. otlp-:host:port?opt1=val2&opt2=val2 +// The function returns a TracerProvider, an io.Closer to close the TracerProvider +// and an error. +// NOTE: If an error occurs, the returned tracer provider is a Noop Tracer Provider. +// Thus, it is always safe to register the returned tracer, even in case of an error. +// This allows to return non-critical errors that can be logged by the caller to debug +// tracer misconfiguration. +func InitializeTracerProvider(ctx context.Context, serviceName string) (trace.TracerProvider, io.Closer, error) { + // We start by registering a noop tracer. + // Since tracing is not required to run Gitaly, we don't want to abort + // Gitaly when tracing cannot be initialized. Thus, if an error occurs + // we will return a NoopTracer. Only if initialization succeed will we + // register a configured tracer provider. + noopTracer := noop.NewTracerProvider() + otel.SetTracerProvider(noopTracer) + + connStr := os.Getenv(gitlabTracingEnvKey) + if connStr == "" { + return noopTracer, io.NopCloser(nil), errTracingDisabled + } + + cfg, err := parseConnectionString(connStr) + if err != nil { + return noopTracer, io.NopCloser(nil), err + } + + cfg.serviceName = serviceName + + var exporter oteltracingsdk.SpanExporter + switch cfg.vendor { + case vendorStackDriver: + exporter, err = stackdriver.New(stackdriver.WithContext(ctx)) + default: + switch cfg.protocol { + case otelHTTPProtocol: + exporter, err = otlptrace.New(ctx, newHTTPOtelClient(cfg)) + case otelGrpcProtocol: + exporter, err = otlptrace.New(ctx, newGrpcOtelClient(cfg)) + default: + err = fmt.Errorf("unsupported protocol: %s", cfg.protocol) + } + } + + if err != nil { + return noopTracer, io.NopCloser(nil), err + } + + // warningError wraps all errors that should be reported for logging but + // that are not critical in initializing the tracer provider. + var warningError error = nil + + // Create a resource to provide additional context to spans + // When creating a new resource, OTEL SDK used `Detectors` to detect + // certain attributes such as PID, Owner ID, etc. + // Some Detector's might fail for various reason, and when that is the + // case, a PartialResource is returned. + // The worst that can happen is a PartialResource returned and some attributes + // empty or missing on spans. In our case, a PartialResource + // is better than nothing, so we accept it and ignore the error. + // One example of a Detector failing is when running unit tests. The user ID + // is often a dummy one, such as 9999. The OwnerDetector cannot find this + // user ID on the system, and thus returns an error. + serviceResource, resErr := resource.New( + ctx, + resource.WithAttributes(attribute.String("service.name", cfg.serviceName)), + resource.WithFromEnv(), + resource.WithTelemetrySDK(), + resource.WithProcess(), + resource.WithOS(), + resource.WithContainer(), + resource.WithHost(), + ) + if resErr != nil { + // wrap the error but do not return, as this error is not critical + // for creating the tracer provider + warningError = fmt.Errorf("error creating resources: %w", err) + } + + // Create a new tracer provider with the exporter configured above + tp := oteltracingsdk.NewTracerProvider( + oteltracingsdk.WithBatcher(exporter), + oteltracingsdk.WithResource(serviceResource), + oteltracingsdk.WithSampler(oteltracingsdk.TraceIDRatioBased(cfg.samplingParam)), + ) + + otel.SetTextMapPropagator(defaultPropagator) + otel.SetTracerProvider(tp) + return tp, newOtelTracerCloser(tp), warningError +} + +func newHTTPOtelClient(cfg tracingConfig, opts ...otlptracehttp.Option) otlptrace.Client { + defaultOps := []otlptracehttp.Option{ + otlptracehttp.WithEndpoint(cfg.endpoint), + otlptracehttp.WithHeaders(cfg.headers), + } + if cfg.insecure { + defaultOps = append(defaultOps, otlptracehttp.WithInsecure()) + } + + return otlptracehttp.NewClient(append(defaultOps, opts...)...) +} + +func newGrpcOtelClient(cfg tracingConfig, opts ...otlptracegrpc.Option) otlptrace.Client { + defaultOps := []otlptracegrpc.Option{ + otlptracegrpc.WithEndpoint(cfg.endpoint), + otlptracegrpc.WithHeaders(cfg.headers), + } + if cfg.insecure { + defaultOps = append(defaultOps, otlptracegrpc.WithInsecure()) + } + + return otlptracegrpc.NewClient(append(defaultOps, opts...)...) +} + +type otelTracerCloser struct { + tp *oteltracingsdk.TracerProvider +} + +func newOtelTracerCloser(tp *oteltracingsdk.TracerProvider) *otelTracerCloser { + return &otelTracerCloser{tp: tp} +} + +func (c *otelTracerCloser) Close() error { + return c.tp.Shutdown(context.Background()) +} diff --git a/internal/tracing/tracer_test.go b/internal/tracing/tracer_test.go new file mode 100644 index 00000000000..39a70fe59da --- /dev/null +++ b/internal/tracing/tracer_test.go @@ -0,0 +1,80 @@ +package tracing + +import ( + "context" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" +) + +// TestInitializeTracerProvider tests the initialization of a tracer +// Because the configuration is already tested in configuration_test.go +// this test does not attempt to test all possible combination of configuration. +func TestInitializeTracerProvider(t *testing.T) { + tests := []struct { + name string + setup func() + wantErr error + }{ + { + name: "tracing disabled", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "") + }, + wantErr: errTracingDisabled, + }, + { + name: "otel-grpc tracer provider", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "otlp-grpc://localhost:1234") + }, + wantErr: nil, + }, + { + name: "otel-http tracer provider", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "otlp-http://localhost:1234") + }, + wantErr: nil, + }, + { + name: "opentracing tracer provider", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "opentracing://jaeger:localhost:1234") + }, + wantErr: nil, + }, + { + name: "invalid tracer provider", + setup: func() { + _ = os.Setenv(gitlabTracingEnvKey, "opentrac://jaeger:localhost:1234") + }, + wantErr: errInvalidConfiguration, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setup() + ctx := context.Background() + + tp, closer, err := InitializeTracerProvider(ctx, "gitaly") + if tt.wantErr != nil { + require.ErrorIs(t, err, tt.wantErr) + assert.IsType(t, tp, noop.TracerProvider{}) + return + } + + assert.NotNil(t, tp) + // For some cases, such as when resource creation fails, an error will be returned + // but this error should not prevent a tracer provider to be created. + // As such, we validate here that the tracer returned is indeed a working tracer provider + // and not a NoopTracer. + assert.IsNotType(t, tp, noop.TracerProvider{}) + assert.NotNil(t, closer) + assert.Nil(t, closer.Close()) + }) + } +} -- GitLab From 5ed4b62734f8f32242c286a6b7ac00414a99f3cf Mon Sep 17 00:00:00 2001 From: Olivier Campeau Date: Thu, 4 Sep 2025 14:39:50 -0400 Subject: [PATCH 2/2] tracing: Use new tracer provider in Gitaly This commit uses the work introduced by the previous one and actually uses the new tracer provider from Gitaly itself. Doing so it removes the dependency on the LabKit library for managing tracer initialization. --- Makefile | 2 +- client/dial_test.go | 279 ++++++++++------- cmd/gitaly-hooks/hooks.go | 25 +- go.mod | 10 +- go.sum | 8 +- internal/cli/dial.go | 2 + internal/cli/gitaly/serve.go | 13 +- internal/cli/praefect/serve.go | 22 +- internal/command/command.go | 41 ++- internal/git/catfile/cache.go | 7 +- internal/git/catfile/tag.go | 9 +- internal/git/catfile/tracing.go | 14 +- internal/git/catfile/tree_entries.go | 15 +- internal/git/gitcmd/command_factory_test.go | 38 ++- .../manager/optimize_repository.go | 4 +- internal/git/trace2/parser.go | 2 +- internal/git/trace2hooks/tracingexporter.go | 13 +- .../git/trace2hooks/tracingexporter_test.go | 154 +++++---- internal/gitaly/server/server.go | 9 +- internal/gitaly/service/repository/license.go | 2 +- .../gitaly/storage/storagemgr/middleware.go | 2 +- .../partition/transaction_manager.go | 19 +- .../transaction_manager_housekeeping.go | 18 +- internal/grpc/client/dial.go | 5 +- internal/grpc/sidechannel/sidechannel.go | 2 +- internal/limiter/concurrency_limiter.go | 7 +- internal/testhelper/tracing.go | 129 +++++--- internal/tracing/configuration.go | 2 +- internal/tracing/middlewares.go | 11 +- internal/tracing/noop.go | 74 ----- internal/tracing/passthrough.go | 97 +++--- internal/tracing/passthrough_test.go | 295 ++++++++++-------- internal/tracing/tracer.go | 2 +- internal/tracing/tracing.go | 53 ++-- internal/tracing/tracing_test.go | 161 +++++----- 35 files changed, 842 insertions(+), 704 deletions(-) delete mode 100644 internal/tracing/noop.go diff --git a/Makefile b/Makefile index 070e88b486f..0f697f27d01 100644 --- a/Makefile +++ b/Makefile @@ -72,7 +72,7 @@ GOLANGCI_LINT_CONFIG ?= ${SOURCE_DIR}/.golangci.yml GITALY_PACKAGE := $(shell go list -m 2>/dev/null || echo unknown) GITALY_VERSION := $(shell ${GIT} describe --match v* 2>/dev/null | sed 's/^v//' || cat ${SOURCE_DIR}/VERSION 2>/dev/null || echo unknown) GO_LDFLAGS := -X ${GITALY_PACKAGE}/internal/version.version=${GITALY_VERSION} -SERVER_BUILD_TAGS := tracer_static,tracer_static_jaeger,tracer_static_stackdriver,continuous_profiler_stackdriver +SERVER_BUILD_TAGS := continuous_profiler_stackdriver ## FIPS_MODE controls whether to build Gitaly and dependencies in FIPS mode. ## Set this to a non-empty value to enable it. diff --git a/client/dial_test.go b/client/dial_test.go index c82503efa16..bedfe0281c0 100644 --- a/client/dial_test.go +++ b/client/dial_test.go @@ -3,6 +3,7 @@ package client import ( "context" "crypto/tls" + "errors" "fmt" "io" "net" @@ -12,20 +13,20 @@ import ( "testing" "github.com/miekg/dns" - "github.com/opentracing/opentracing-go" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" gitalyauth "gitlab.com/gitlab-org/gitaly/v18/auth" internalclient "gitlab.com/gitlab-org/gitaly/v18/internal/grpc/client" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" - "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" gitalyx509 "gitlab.com/gitlab-org/gitaly/v18/internal/x509" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" "gitlab.com/gitlab-org/labkit/correlation" grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc" - grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/baggage" + "go.opentelemetry.io/otel/propagation" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" @@ -359,59 +360,74 @@ func TestDial_Correlation(t *testing.T) { } func TestDial_Tracing(t *testing.T) { - serverSocketPath := testhelper.GetTemporaryGitalySocketFileName(t) + const serviceBaggageKey = "service" - listener, err := net.Listen("unix", serverSocketPath) - require.NoError(t, err) + reporter := testhelper.NewStubTracingReporter(t) + defer testhelper.MustClose(t, reporter) - clientSendClosed := make(chan struct{}) + getSvc := func() *testSvc { + return &testSvc{ + unaryCall: func(ctx context.Context, r *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) { + spanCtx, span := reporter.TracerProvider().Tracer("server").Start(ctx, "nested-span-unary") + defer span.End() - // This is our test service. All it does is to create additional spans - // which should in the end be visible when collecting all registered - // spans. - grpcServer := grpc.NewServer( - grpc.UnaryInterceptor(grpctracing.UnaryServerTracingInterceptor()), - grpc.StreamInterceptor(grpctracing.StreamServerTracingInterceptor()), - ) - svc := &testSvc{ - unaryCall: func(ctx context.Context, r *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) { - span, _ := tracing.StartSpan(ctx, "nested-span", nil) - defer span.Finish() - span.LogKV("was", "called") - return &grpc_testing.SimpleResponse{}, nil - }, - fullDuplexCall: func(stream grpc_testing.TestService_FullDuplexCallServer) error { - // synchronize the client has returned from CloseSend as the client span finishing - // races with sending the stream close to the server - select { - case <-clientSendClosed: - case <-stream.Context().Done(): - return stream.Context().Err() - } + b := baggage.FromContext(spanCtx) + serviceFromBaggage := b.Member(serviceBaggageKey) + attributeFromBaggage := attribute.String(serviceBaggageKey, serviceFromBaggage.Value()) + span.SetAttributes(attributeFromBaggage) + return &grpc_testing.SimpleResponse{}, nil + }, + fullDuplexCall: func(stream grpc_testing.TestService_FullDuplexCallServer) error { + spanCtx, span := reporter.TracerProvider().Tracer("server").Start(stream.Context(), "nested-span-full-duplex") + defer span.End() + + // set attributes to span + b := baggage.FromContext(spanCtx) + serviceFromBaggage := b.Member(serviceBaggageKey) + attributeFromBaggage := attribute.String(serviceBaggageKey, serviceFromBaggage.Value()) + span.SetAttributes(attributeFromBaggage) + + // process message + for { + _, err := stream.Recv() + if errors.Is(err, io.EOF) { + break + } + } - span, _ := tracing.StartSpan(stream.Context(), "nested-span", nil) - defer span.Finish() - span.LogKV("was", "called") - return nil - }, + return nil + }, + } } - grpc_testing.RegisterTestServiceServer(grpcServer, svc) - go testhelper.MustServe(t, grpcServer, listener) - defer grpcServer.Stop() - ctx := testhelper.Context(t) + propagator := propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + ) t.Run("unary", func(t *testing.T) { - reporter := jaeger.NewInMemoryReporter() - tracer, tracerCloser := jaeger.NewTracer("", jaeger.NewConstSampler(true), reporter) - defer testhelper.MustClose(t, tracerCloser) + reporter.Reset() - defer func(old opentracing.Tracer) { opentracing.SetGlobalTracer(old) }(opentracing.GlobalTracer()) - opentracing.SetGlobalTracer(tracer) + grpcServer := grpc.NewServer( + grpc.StatsHandler(otelgrpc.NewServerHandler( + otelgrpc.WithPropagators(propagator), + otelgrpc.WithTracerProvider(reporter.TracerProvider()), + )), + ) + grpc_testing.RegisterTestServiceServer(grpcServer, getSvc()) + + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName(t) + + listener, err := net.Listen("unix", serverSocketPath) + require.NoError(t, err) + go testhelper.MustServe(t, grpcServer, listener) + defer grpcServer.Stop() + ctx := testhelper.Context(t) - // This needs to be run after setting up the global tracer as it will cause us to - // create the span when executing the RPC call further down below. cc, err := DialContext(ctx, "unix://"+serverSocketPath, []grpc.DialOption{ + grpc.WithStatsHandler(otelgrpc.NewClientHandler( + otelgrpc.WithTracerProvider(reporter.TracerProvider()), + otelgrpc.WithPropagators(propagator))), internalclient.UnaryInterceptor(), internalclient.StreamInterceptor(), WithGitalyDNSResolver(DefaultDNSResolverBuilderConfig()), @@ -422,57 +438,84 @@ func TestDial_Tracing(t *testing.T) { // We set up a "main" span here, which is going to be what the // other spans inherit from. In order to check whether baggage // works correctly, we also set up a "stub" baggage item which - // should be inherited to child contexts. - span := tracer.StartSpan("unary-check") - span = span.SetBaggageItem("service", "stub") - ctx := opentracing.ContextWithSpan(ctx, span) - - // We're now invoking the unary RPC with the span injected into - // the context. This should create a span that's nested into - // the "stream-check" span. - _, err = grpc_testing.NewTestServiceClient(cc).UnaryCall(ctx, &grpc_testing.SimpleRequest{}) + // should be inherited to child contexts. It would be the + // responsibility of the other processes to inspect the baggage + // and add it as attribute to a child span. + globalTracer := reporter.TracerProvider().Tracer("test") + rootSpanCtx, rootSpan := globalTracer.Start(ctx, "root-span") + + m0, err := baggage.NewMember(serviceBaggageKey, "stub") + require.NoError(t, err) + + b, err := baggage.New(m0) + require.NoError(t, err) + + rootSpanCtx = baggage.ContextWithBaggage(rootSpanCtx, b) + + // We're now invoking the unary RPC with the span injected into the context. + _, err = grpc_testing.NewTestServiceClient(cc).UnaryCall(rootSpanCtx, &grpc_testing.SimpleRequest{}) require.NoError(t, err) - span.Finish() + rootSpan.End() - spans := reporter.GetSpans() - require.Len(t, spans, 3) + recorderSpans := reporter.GetSpans() - for i, expectedSpan := range []struct { - baggage string - operation string + require.Len(t, recorderSpans, 4) + + for i, expectedSpanSpecs := range []struct { + operation string + attributeValue string }{ // This is the first span we expect, which is the - // "health" span which we've manually created inside of - // PingMethod. - {baggage: "", operation: "nested-span"}, - // This span is the RPC call to TestService/Ping. It - // inherits the "unary-check" we set up and thus has - // baggage. - {baggage: "stub", operation: "/grpc.testing.TestService/UnaryCall"}, - // And this finally is the outermost span which we - // manually set up before the RPC call. - {baggage: "stub", operation: "unary-check"}, + // _operation_ span created inside the gRPC handler + {operation: "nested-span-unary", attributeValue: "stub"}, + // The next two spans are the client span and the server span + // added automatically by otel instrumentation + {operation: "grpc.testing.TestService/UnaryCall", attributeValue: ""}, + {operation: "grpc.testing.TestService/UnaryCall", attributeValue: ""}, + // This is the root span, the first one we created at the beginning + // of the test. + {operation: "root-span", attributeValue: ""}, } { - assert.IsType(t, spans[i], &jaeger.Span{}) - span := spans[i].(*jaeger.Span) + span := recorderSpans[i] + + serviceAttribute := attribute.KeyValue{} + for _, attr := range span.Attributes { + if string(attr.Key) == serviceBaggageKey { + serviceAttribute = attr + } + } - assert.Equal(t, expectedSpan.baggage, span.BaggageItem("service"), "wrong baggage item for span %d", i) - assert.Equal(t, expectedSpan.operation, span.OperationName(), "wrong operation name for span %d", i) + require.Equal(t, serviceAttribute.Value.AsString(), expectedSpanSpecs.attributeValue) + assert.Equal(t, expectedSpanSpecs.operation, span.Name, "wrong operation name for span %d", i) } }) t.Run("stream", func(t *testing.T) { - reporter := jaeger.NewInMemoryReporter() - tracer, tracerCloser := jaeger.NewTracer("", jaeger.NewConstSampler(true), reporter) - defer testhelper.MustClose(t, tracerCloser) + reporter.Reset() - defer func(old opentracing.Tracer) { opentracing.SetGlobalTracer(old) }(opentracing.GlobalTracer()) - opentracing.SetGlobalTracer(tracer) + grpcServer := grpc.NewServer( + grpc.StatsHandler(otelgrpc.NewServerHandler( + otelgrpc.WithTracerProvider(reporter.TracerProvider()), + otelgrpc.WithPropagators(propagator), + )), + ) + grpc_testing.RegisterTestServiceServer(grpcServer, getSvc()) + + serverSocketPath := testhelper.GetTemporaryGitalySocketFileName(t) + + listener, err := net.Listen("unix", serverSocketPath) + require.NoError(t, err) + go testhelper.MustServe(t, grpcServer, listener) + defer grpcServer.Stop() + ctx := testhelper.Context(t) // This needs to be run after setting up the global tracer as it will cause us to // create the span when executing the RPC call further down below. cc, err := DialContext(ctx, "unix://"+serverSocketPath, []grpc.DialOption{ + grpc.WithStatsHandler(otelgrpc.NewClientHandler( + otelgrpc.WithTracerProvider(reporter.TracerProvider()), + otelgrpc.WithPropagators(propagator))), internalclient.UnaryInterceptor(), internalclient.StreamInterceptor(), WithGitalyDNSResolver(DefaultDNSResolverBuilderConfig()), @@ -480,53 +523,65 @@ func TestDial_Tracing(t *testing.T) { require.NoError(t, err) defer testhelper.MustClose(t, cc) - // We set up a "main" span here, which is going to be what the other spans inherit - // from. In order to check whether baggage works correctly, we also set up a "stub" - // baggage item which should be inherited to child contexts. - span := tracer.StartSpan("stream-check") - span = span.SetBaggageItem("service", "stub") - ctx := opentracing.ContextWithSpan(ctx, span) + // We set up a "main" span here, which is going to be what the + // other spans inherit from. In order to check whether baggage + // works correctly, we also set up a "stub" baggage item which + // should be inherited to child contexts. It would be the + // responsibility of the other processes to inspect the baggage + // and add it as attribute to a child span. + globalTracer := reporter.TracerProvider().Tracer("test") + rootSpanCtx, rootSpan := globalTracer.Start(ctx, "root-span") + + m0, err := baggage.NewMember(serviceBaggageKey, "stub") + require.NoError(t, err) + + b, err := baggage.New(m0) + require.NoError(t, err) + + rootSpanCtx = baggage.ContextWithBaggage(rootSpanCtx, b) // We're now invoking the streaming RPC with the span injected into the context. // This should create a span that's nested into the "stream-check" span. - stream, err := grpc_testing.NewTestServiceClient(cc).FullDuplexCall(ctx) + stream, err := grpc_testing.NewTestServiceClient(cc).FullDuplexCall(rootSpanCtx) require.NoError(t, err) require.NoError(t, stream.CloseSend()) - close(clientSendClosed) // wait for the server to finish its spans and close the stream - resp, err := stream.Recv() + _, err = stream.Recv() require.Equal(t, err, io.EOF) - require.Nil(t, resp) - span.Finish() + rootSpan.End() + + recorderSpans := reporter.GetSpans() - spans := reporter.GetSpans() - require.Len(t, spans, 3) + require.Len(t, recorderSpans, 4) - for i, expectedSpan := range []struct { - baggage string - operation string + for i, expectedSpanSpecs := range []struct { + operation string + attributeValue string }{ - // This span is the RPC call to TestService/Ping. - {baggage: "stub", operation: "/grpc.testing.TestService/FullDuplexCall"}, - // This is the second span we expect, which is the "nested-span" span which - // we've manually created inside of PingMethod. This is different than for - // unary RPCs: given that one can send multiple messages to the RPC, we may - // see multiple such "nested-span"s being created. And the PingStream span - // will only be finalized last. - {baggage: "", operation: "nested-span"}, - // And this finally is the outermost span which we - // manually set up before the RPC call. - {baggage: "stub", operation: "stream-check"}, + // This is the first span we expect, which is the + // _operation_ span created inside the gRPC handler + {operation: "nested-span-full-duplex", attributeValue: "stub"}, + // The next two spans are the client span and the server span + // added automatically by otel instrumentation + {operation: "grpc.testing.TestService/FullDuplexCall", attributeValue: ""}, + {operation: "grpc.testing.TestService/FullDuplexCall", attributeValue: ""}, + // This is the root span, the first one we created at the beginning + // of the test. + {operation: "root-span", attributeValue: ""}, } { - if !assert.IsType(t, spans[i], &jaeger.Span{}) { - continue + span := recorderSpans[i] + + serviceAttribute := attribute.KeyValue{} + for _, attr := range span.Attributes { + if string(attr.Key) == serviceBaggageKey { + serviceAttribute = attr + } } - span := spans[i].(*jaeger.Span) - assert.Equal(t, expectedSpan.baggage, span.BaggageItem("service"), "wrong baggage item for span %d", i) - assert.Equal(t, expectedSpan.operation, span.OperationName(), "wrong operation name for span %d", i) + require.Equal(t, serviceAttribute.Value.AsString(), expectedSpanSpecs.attributeValue) + assert.Equal(t, expectedSpanSpecs.operation, span.Name, "wrong operation name for span %d", i) } }) } diff --git a/cmd/gitaly-hooks/hooks.go b/cmd/gitaly-hooks/hooks.go index d67ec8d951c..8e77500b059 100644 --- a/cmd/gitaly-hooks/hooks.go +++ b/cmd/gitaly-hooks/hooks.go @@ -159,6 +159,9 @@ func executeHook(ctx context.Context, cmd hookCommand, args []string) error { ctx = injectMetadataIntoOutgoingCtx(ctx, payload) + tracerCloser := initializeTracing() + defer func() { _ = tracerCloser.Close() }() + conn, err := dialGitaly(ctx, payload) if err != nil { return fmt.Errorf("error when connecting to gitaly: %w", err) @@ -209,15 +212,15 @@ func dialGitaly(ctx context.Context, payload gitcmd.HooksPayload) (*grpc.ClientC ), } - // Setup tracing is possible - initializeTracing() - if spanContext, err := tracing.ExtractSpanContextFromEnv(os.Environ()); err == nil { + if spanContext, err := tracing.PropagateFromEnv(os.Environ()); err == nil { unaryInterceptors = append(unaryInterceptors, tracing.UnaryPassthroughInterceptor(spanContext)) streamInterceptors = append(streamInterceptors, tracing.StreamPassthroughInterceptor(spanContext)) } - dialOpts = append(dialOpts, grpc.WithChainUnaryInterceptor(unaryInterceptors...)) - dialOpts = append(dialOpts, grpc.WithChainStreamInterceptor(streamInterceptors...)) + dialOpts = append(dialOpts, + grpc.WithChainUnaryInterceptor(unaryInterceptors...), + grpc.WithChainStreamInterceptor(streamInterceptors...), + ) conn, err := client.New(ctx, "unix://"+payload.InternalSocket, client.WithGrpcOptions(dialOpts)) if err != nil { return nil, fmt.Errorf("error when dialing: %w", err) @@ -226,13 +229,12 @@ func dialGitaly(ctx context.Context, payload gitcmd.HooksPayload) (*grpc.ClientC return conn, nil } -func initializeTracing() { +func initializeTracing() io.Closer { // All stdout and stderr are captured by Gitaly process. They may be sent back to users. // We don't want to bother them with these redundant logs. As a result, all logs should be - // suppressed while labkit is in initialization phase. + // suppressed while tracing is in initialization phase. // - //nolint:forbidigo // LabKit does not allow us to supply our own logger, so we must modify the standard logger - // instead. + //nolint:forbidigo output := logrus.StandardLogger().Out logrus.SetOutput(io.Discard) defer logrus.SetOutput(output) @@ -242,8 +244,9 @@ func initializeTracing() { // or starting new span. This technique connects the parent span in parent Gitaly process // and the remote spans when Gitaly handle subsequent gRPC calls issued by this hook. // As tracing is a nice-to-have feature, it should not interrupt the main functionality - // of Gitaly hook. As a result, errors, if any, are ignored. - labkittracing.Initialize() + // of Gitaly hook. As a result, errors, if any, are logged but not returned. + _, closer, _ := tracing.InitializeTracerProvider(context.Background(), "gitaly-hooks") + return closer } func gitPushOptions() []string { diff --git a/go.mod b/go.mod index 084075b245c..6740c43f1a1 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,6 @@ require ( github.com/miekg/dns v1.1.68 github.com/olekukonko/tablewriter v1.0.7 github.com/opencontainers/runtime-spec v1.2.1 - github.com/opentracing/opentracing-go v1.2.0 github.com/pelletier/go-toml/v2 v2.2.4 github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_model v0.6.2 @@ -40,7 +39,6 @@ require ( github.com/rubenv/sql-migrate v1.8.0 github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.11.1 - github.com/uber/jaeger-client-go v2.30.0+incompatible github.com/urfave/cli/v3 v3.3.3 gitlab.com/gitlab-org/labkit v1.31.2 go.etcd.io/raft/v3 v3.6.0 @@ -59,7 +57,6 @@ require ( gocloud.dev v0.40.1-0.20241107185025-56954848c3aa golang.org/x/crypto v0.42.0 golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 - golang.org/x/net v0.44.0 // indirect golang.org/x/sync v0.17.0 golang.org/x/sys v0.37.0 golang.org/x/text v0.30.0 @@ -197,6 +194,7 @@ require ( github.com/olekukonko/ll v0.0.8 // indirect github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect github.com/onsi/gomega v1.35.1 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect @@ -204,8 +202,8 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/prometheus/procfs v0.17.0 // indirect - github.com/prometheus/prometheus v0.303.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/prometheus v0.54.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 // indirect @@ -220,6 +218,7 @@ require ( github.com/tinylib/msgp v1.3.0 // indirect github.com/tklauser/go-sysconf v0.3.15 // indirect github.com/tklauser/numcpus v0.10.0 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect @@ -235,6 +234,7 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.44.0 // indirect golang.org/x/oauth2 v0.31.0 // indirect golang.org/x/tools v0.37.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect diff --git a/go.sum b/go.sum index e4870b5df98..1a771add8ac 100644 --- a/go.sum +++ b/go.sum @@ -592,10 +592,10 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI= github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= -github.com/prometheus/prometheus v0.303.1 h1:He/2jRE6sB23Ew38AIoR1WRR3fCMgPlJA2E0obD2WSY= -github.com/prometheus/prometheus v0.303.1/go.mod h1:WEq2ogBPZoLjj9x5K67VEk7ECR0nRD9XCjaOt1lsYck= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/prometheus v0.54.0 h1:6+VmEkohHcofl3W5LyRlhw1Lfm575w/aX6ZFyVAmzM0= +github.com/prometheus/prometheus v0.54.0/go.mod h1:xlLByHhk2g3ycakQGrMaU8K7OySZx98BzeCR99991NY= github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= diff --git a/internal/cli/dial.go b/internal/cli/dial.go index 8f05418c82d..d973e640d55 100644 --- a/internal/cli/dial.go +++ b/internal/cli/dial.go @@ -6,6 +6,7 @@ import ( gitalyauth "gitlab.com/gitlab-org/gitaly/v18/auth" "gitlab.com/gitlab-org/gitaly/v18/internal/grpc/client" + "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "google.golang.org/grpc" ) @@ -16,6 +17,7 @@ func Dial(ctx context.Context, addr, token string, timeout time.Duration, opts . defer cancel() opts = append(opts, + grpc.WithStatsHandler(tracing.NewGRPCClientStatsHandler()), client.UnaryInterceptor(), client.StreamInterceptor(), ) diff --git a/internal/cli/gitaly/serve.go b/internal/cli/gitaly/serve.go index 194d80ec56b..0531be28fa4 100644 --- a/internal/cli/gitaly/serve.go +++ b/internal/cli/gitaly/serve.go @@ -61,7 +61,6 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/version" "gitlab.com/gitlab-org/labkit/fips" "gitlab.com/gitlab-org/labkit/monitoring" - labkittracing "gitlab.com/gitlab-org/labkit/tracing" "go.uber.org/automaxprocs/maxprocs" "gocloud.dev/blob" "google.golang.org/grpc" @@ -111,6 +110,12 @@ func serveAction(ctx context.Context, cmd *cli.Command) error { return cli.Exit(err, 1) } + _, tracingCloser, err := tracing.InitializeTracerProvider(context.Background(), "gitaly") + if err != nil { + logger.WithError(err).Error("error initializing distributed tracing") + } + defer func() { _ = tracingCloser.Close() }() + if cfg.Auth.Transitioning && len(cfg.Auth.Token) > 0 { logger.Warn("Authentication is enabled but not enforced because transitioning=true. Gitaly will accept unauthenticated requests.") } @@ -163,7 +168,6 @@ func configure(configPath string) (config.Cfg, log.Logger, error) { sentry.ConfigureSentry(logger, version.GetVersion(), sentry.Config(cfg.Logging.Sentry)) cfg.Prometheus.Configure(logger) - labkittracing.Initialize(labkittracing.WithServiceName("gitaly")) preloadLicenseDatabase(logger) return cfg, logger, nil @@ -189,7 +193,7 @@ func run(appCtx *cli.Command, cfg config.Cfg, logger log.Logger) error { beganRun := time.Now() bootstrapSpan, ctx := tracing.StartSpan(ctx, "gitaly-bootstrap", nil) - defer bootstrapSpan.Finish() + defer bootstrapSpan.End() if cfg.RuntimeDir != "" { if err := config.PruneOldGitalyProcessDirectories(logger, cfg.RuntimeDir); err != nil { @@ -277,6 +281,7 @@ func run(appCtx *cli.Command, cfg config.Cfg, logger log.Logger) error { }, )), client.WithDialOptions( + grpc.WithStatsHandler(tracing.NewGRPCClientStatsHandler()), client.UnaryInterceptor(), client.StreamInterceptor(), ), @@ -773,7 +778,7 @@ func run(appCtx *cli.Command, cfg config.Cfg, logger log.Logger) error { if err := b.Start(); err != nil { return fmt.Errorf("unable to start the bootstrap: %w", err) } - bootstrapSpan.Finish() + bootstrapSpan.End() // There are a few goroutines running async tasks that may still be in progress (i.e. preloading the license // database), but this is a close enough indication of startup latency. logger.WithField("duration_ms", time.Since(beganRun).Milliseconds()).Info("Started Gitaly") diff --git a/internal/cli/praefect/serve.go b/internal/cli/praefect/serve.go index 337d053bf94..157af3435ad 100644 --- a/internal/cli/praefect/serve.go +++ b/internal/cli/praefect/serve.go @@ -96,6 +96,9 @@ func serveAction(ctx context.Context, cmd *cli.Command) error { func run(conf config.Config, appName string, logger log.Logger) error { configure(logger, appName, conf) + tracerCloser := tracing.Initialize(tracing.WithServiceName(appName)) + defer func() { _ = tracerCloser.Close() }() + starterConfigs, err := getStarterConfigs(conf) if err != nil { return cli.Exit(err, 1) @@ -140,8 +143,6 @@ func readConfig(path string) (config.Config, error) { } func configure(logger log.Logger, appName string, conf config.Config) { - tracing.Initialize(tracing.WithServiceName(appName)) - if conf.PrometheusListenAddr != "" { conf.Prometheus.Configure(logger) } @@ -149,17 +150,12 @@ func configure(logger log.Logger, appName string, conf config.Config) { sentry.ConfigureSentry(logger, version.GetVersion(), conf.Sentry) } -func server( - cfgs []starter.Config, - conf config.Config, - logger log.Logger, - b bootstrap.Listener, - promreg prometheus.Registerer, - dbPromRegistry interface { - prometheus.Registerer - prometheus.Gatherer - }, -) error { +type dbPromRegistryWrapper interface { + prometheus.Registerer + prometheus.Gatherer +} + +func server(cfgs []starter.Config, conf config.Config, logger log.Logger, b bootstrap.Listener, promreg prometheus.Registerer, dbPromRegistry dbPromRegistryWrapper) error { nodeLatencyHistogram, err := metrics.RegisterNodeLatency(conf.Prometheus, promreg) if err != nil { return err diff --git a/internal/command/command.go b/internal/command/command.go index af9a9e11866..798ffd1423b 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -15,7 +15,6 @@ import ( "syscall" "time" - "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "gitlab.com/gitlab-org/gitaly/v18/internal/command/commandcounter" @@ -24,6 +23,8 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/log" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" labkittracing "gitlab.com/gitlab-org/labkit/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) var ( @@ -153,7 +154,7 @@ type Command struct { finalizers []func(context.Context, *Command) - span opentracing.Span + span trace.Span metricsCmd string metricsSubCmd string @@ -230,9 +231,9 @@ func New(ctx context.Context, logger log.Logger, nameAndArgs []string, opts ...O span, ctx := tracing.StartSpanIfHasParent( ctx, spanName, - tracing.Tags{ - "path": nameAndArgs[0], - "args": strings.Join(nameAndArgs[1:], " "), + []attribute.KeyValue{ + attribute.String("path", nameAndArgs[0]), + attribute.String("args", strings.Join(nameAndArgs[1:], " ")), }, ) cmd := exec.Command(nameAndArgs[0], nameAndArgs[1:]...) @@ -701,22 +702,28 @@ func (c *Command) logProcessComplete() { contextSwitchesTotal.WithLabelValues(service, method, cmdName, c.metricsSubCmd, "nonvoluntary", c.cmdGitVersion, c.refBackend).Add(float64(rusage.Nivcsw)) } - c.span.SetTag("pid", cmd.ProcessState.Pid()) - c.span.SetTag("exit_code", exitCode) - c.span.SetTag("system_time_ms", systemTime.Milliseconds()) - c.span.SetTag("user_time_ms", userTime.Milliseconds()) - c.span.SetTag("real_time_ms", realTime.Milliseconds()) + attributes := []attribute.KeyValue{ + attribute.Int("pid", cmd.ProcessState.Pid()), + attribute.Int("exit_code", exitCode), + attribute.Int64("system_time_ms", systemTime.Milliseconds()), + attribute.Int64("user_time_ms", userTime.Milliseconds()), + attribute.Int64("real_time_ms", realTime.Milliseconds()), + } + if ok { - c.span.SetTag("maxrss", rusage.Maxrss) - c.span.SetTag("inblock", rusage.Inblock) - c.span.SetTag("oublock", rusage.Oublock) - c.span.SetTag("minflt", rusage.Minflt) - c.span.SetTag("majflt", rusage.Majflt) + attributes = append(attributes, + attribute.Int64("maxrss", rusage.Maxrss), + attribute.Int64("inblock", rusage.Inblock), + attribute.Int64("oublock", rusage.Oublock), + attribute.Int64("minflt", rusage.Minflt), + attribute.Int64("majflt", rusage.Majflt), + ) } if c.cgroupPath != "" { - c.span.SetTag("cgroup_path", c.cgroupPath) + attributes = append(attributes, attribute.String("cgroup_path", c.cgroupPath)) } - c.span.Finish() + c.span.SetAttributes(attributes...) + c.span.End() } // Args is an accessor for the command arguments diff --git a/internal/git/catfile/cache.go b/internal/git/catfile/cache.go index f256beaf187..5ebd5543e41 100644 --- a/internal/git/catfile/cache.go +++ b/internal/git/catfile/cache.go @@ -15,6 +15,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/helper" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/labkit/correlation" + "go.opentelemetry.io/otel/attribute" ) const ( @@ -239,7 +240,7 @@ func (c *ProcessCache) getOrCreateProcess( defer c.reportCacheMembers() span, ctx := tracing.StartSpanIfHasParent(ctx, spanName, nil) - defer span.Finish() + defer span.End() cacheKey, isCacheable := newCacheKey(fmt.Sprintf("%d", roundToNearestFiveMinute(time.Now())), repo) @@ -252,14 +253,14 @@ func (c *ProcessCache) getOrCreateProcess( if entry, ok := processes.Checkout(cacheKey); ok { c.catfileCacheCounter.WithLabelValues("hit").Inc() - span.SetTag("hit", true) + span.SetAttributes(attribute.Bool("hit", true)) return entry.value, func() { c.returnToCache(processes, cacheKey, entry.value, entry.cancel) }, nil } c.catfileCacheCounter.WithLabelValues("miss").Inc() - span.SetTag("hit", false) + span.SetAttributes(attribute.Bool("hit", false)) // When cache misses, a new process is created. This process may be re-used later. // In that case, the lifecycle of the process is stretched across multiple diff --git a/internal/git/catfile/tag.go b/internal/git/catfile/tag.go index db86ba4c939..19aef60bd5f 100644 --- a/internal/git/catfile/tag.go +++ b/internal/git/catfile/tag.go @@ -10,14 +10,19 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/helper" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" + "go.opentelemetry.io/otel/attribute" ) // GetTag looks up a commit by tagID using an existing catfile.Batch instance. Note: we pass // in the tagName because the tag name from refs/tags may be different than the name found in the // actual tag object. We want to use the tagName found in refs/tags func GetTag(ctx context.Context, objectReader ObjectContentReader, tagID git.Revision, tagName string) (*gitalypb.Tag, error) { - span, ctx := tracing.StartSpanIfHasParent(ctx, "catfile.GetTag", tracing.Tags{"tagName": tagName}) - defer span.Finish() + span, ctx := tracing.StartSpanIfHasParent(ctx, + "catfile.GetTag", + []attribute.KeyValue{ + attribute.String("tagName", tagName), + }) + defer span.End() object, err := objectReader.Object(ctx, tagID) if err != nil { diff --git a/internal/git/catfile/tracing.go b/internal/git/catfile/tracing.go index f668b922d33..d5c43fe26ae 100644 --- a/internal/git/catfile/tracing.go +++ b/internal/git/catfile/tracing.go @@ -4,13 +4,15 @@ import ( "context" "sync" - "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/prometheus" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" + "go.opentelemetry.io/otel/attribute" + oteltrace "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) type trace struct { - span opentracing.Span + span oteltrace.Span counter *prometheus.CounterVec requestsLock sync.Mutex @@ -25,9 +27,9 @@ func startTrace( counter *prometheus.CounterVec, methodName string, ) *trace { - var span opentracing.Span + var span oteltrace.Span if methodName == "" { - span = &tracing.NoopSpan{} + span = noop.Span{} } else { span, _ = tracing.StartSpanIfHasParent(ctx, methodName, nil) } @@ -62,11 +64,11 @@ func (t *trace) finish() { continue } - t.span.SetTag(requestType, requestCount) + t.span.SetAttributes(attribute.Int(requestType, requestCount)) if t.counter != nil { t.counter.WithLabelValues(requestType).Add(float64(requestCount)) } } - t.span.Finish() + t.span.End() } diff --git a/internal/git/catfile/tree_entries.go b/internal/git/catfile/tree_entries.go index 8a538043dd6..49e6a757193 100644 --- a/internal/git/catfile/tree_entries.go +++ b/internal/git/catfile/tree_entries.go @@ -14,6 +14,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/git" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" + "go.opentelemetry.io/otel/attribute" ) type revisionPath struct{ revision, path string } @@ -37,9 +38,12 @@ func (tef *TreeEntryFinder) FindByRevisionAndPath(ctx context.Context, revision, span, ctx := tracing.StartSpanIfHasParent( ctx, "catfile.FindByRevisionAndPatch", - tracing.Tags{"revision": revision, "path": path}, + []attribute.KeyValue{ + attribute.String("revision", revision), + attribute.String("path", path), + }, ) - defer span.Finish() + defer span.End() dir := pathPkg.Dir(path) cacheKey := revisionPath{revision: revision, path: dir} @@ -121,9 +125,12 @@ func TreeEntries( span, ctx := tracing.StartSpanIfHasParent( ctx, "catfile.TreeEntries", - tracing.Tags{"revision": revision, "path": path}, + []attribute.KeyValue{ + attribute.String("revision", revision), + attribute.String("path", path), + }, ) - defer span.Finish() + defer span.End() if path == "." { path = "" diff --git a/internal/git/gitcmd/command_factory_test.go b/internal/git/gitcmd/command_factory_test.go index 22334d500a3..12ccc62b0bd 100644 --- a/internal/git/gitcmd/command_factory_test.go +++ b/internal/git/gitcmd/command_factory_test.go @@ -25,6 +25,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper/testcfg" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" + "go.opentelemetry.io/otel" "golang.org/x/time/rate" ) @@ -949,8 +950,10 @@ func TestTrace2TracingExporter(t *testing.T) { { desc: "active span is sampled", setup: func(t *testing.T, ctx context.Context) context.Context { - _, ctx = tracing.StartSpan(ctx, "root", nil) - return ctx + tr := otel.GetTracerProvider().Tracer(t.Name()) + spanCtx, span := tr.Start(ctx, "root") + span.End() + return spanCtx }, assert: func(t *testing.T, spans []string, statFields map[string]any) { require.Equal(t, statFields["trace2.activated"], "true") @@ -968,8 +971,10 @@ func TestTrace2TracingExporter(t *testing.T) { desc: "active span is not sampled", tracerOptions: []testhelper.StubTracingReporterOption{testhelper.NeverSampled()}, setup: func(t *testing.T, ctx context.Context) context.Context { - _, ctx = tracing.StartSpan(ctx, "root", nil) - return ctx + tr := otel.GetTracerProvider().Tracer(t.Name()) + spanCtx, span := tr.Start(ctx, "root") + span.End() + return spanCtx }, assert: func(t *testing.T, spans []string, statFields map[string]any) { require.NotContains(t, statFields, "trace2.activated") @@ -981,8 +986,8 @@ func TestTrace2TracingExporter(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t, tc.tracerOptions...) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t, tc.tracerOptions...) + defer func() { _ = reporter.Close() }() ctx := tc.setup(t, log.InitContextCustomFields(testhelper.Context(t))) performRevList(t, ctx) @@ -992,8 +997,8 @@ func TestTrace2TracingExporter(t *testing.T) { statFields := customFields.Fields() var spans []string - for _, span := range testhelper.ReportedSpans(t, reporter) { - spans = append(spans, span.Operation) + for _, span := range reporter.GetSpans() { + spans = append(spans, span.Name) } tc.assert(t, spans, statFields) @@ -1085,7 +1090,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { desc: "active span is sampled", subCmd: "status", setup: func(t *testing.T) (context.Context, []trace2.Hook) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + defer span.End() return ctx, []trace2.Hook{ trace2hooks.NewTracingExporter(), } @@ -1095,7 +1101,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { desc: "active span is not sampled", subCmd: "status", setup: func(t *testing.T) (context.Context, []trace2.Hook) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + defer span.End() return ctx, []trace2.Hook{} }, tracerOptions: []testhelper.StubTracingReporterOption{testhelper.NeverSampled()}, @@ -1113,7 +1120,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { desc: "subcmd is pack-objects and active span is sampled", subCmd: "pack-objects", setup: func(t *testing.T) (context.Context, []trace2.Hook) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + defer span.End() hooks := []trace2.Hook{ trace2hooks.NewTracingExporter(), trace2hooks.NewPackObjectsMetrics(), @@ -1128,8 +1136,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { setup: func(t *testing.T) (context.Context, []trace2.Hook) { ctx := testhelper.Context(t) ctx = featureflag.ContextWithFeatureFlag(ctx, featureflag.LogGitTraces, true) - _, ctx = tracing.StartSpan(ctx, "root", nil) - + span, ctx := tracing.StartSpan(ctx, "root", nil) + defer span.End() hooks := []trace2.Hook{ trace2hooks.NewTracingExporter(), trace2hooks.NewPackObjectsMetrics(), @@ -1141,8 +1149,8 @@ func TestDefaultTrace2HooksFor(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - _, cleanup := testhelper.StubTracingReporter(t, tc.tracerOptions...) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t, tc.tracerOptions...) + defer func() { _ = reporter.Close() }() ctx, expectedHooks := tc.setup(t) hooks := gitcmd.DefaultTrace2HooksFor(ctx, tc.subCmd, testhelper.SharedLogger(t), rate.NewLimiter(1, 1)) diff --git a/internal/git/housekeeping/manager/optimize_repository.go b/internal/git/housekeeping/manager/optimize_repository.go index 7b68cb3f6dc..c33040162ed 100644 --- a/internal/git/housekeeping/manager/optimize_repository.go +++ b/internal/git/housekeeping/manager/optimize_repository.go @@ -57,7 +57,7 @@ func (m *RepositoryManager) OptimizeRepository( } span, ctx := tracing.StartSpanIfHasParent(ctx, "housekeeping.OptimizeRepository", nil) - defer span.Finish() + defer span.End() if err := m.maybeStartTransaction(ctx, repo, func(ctx context.Context, tx storage.Transaction, repo *localrepo.Repo) error { originalRepo := &gitalypb.Repository{ @@ -465,7 +465,7 @@ func (m *RepositoryManager) packRefsIfNeeded(ctx context.Context, repo *localrep // CleanStaleData removes any stale data in the repository as per the provided configuration. func (m *RepositoryManager) CleanStaleData(ctx context.Context, repo *localrepo.Repo, cfg housekeeping.CleanStaleDataConfig) error { span, ctx := tracing.StartSpanIfHasParent(ctx, "housekeeping.CleanStaleData", nil) - defer span.Finish() + defer span.End() repoPath, err := repo.Path(ctx) if err != nil { diff --git a/internal/git/trace2/parser.go b/internal/git/trace2/parser.go index b999bc9f7a9..c72ebed00e9 100644 --- a/internal/git/trace2/parser.go +++ b/internal/git/trace2/parser.go @@ -37,7 +37,7 @@ import ( // For more information, please visit Trace2 API: https://git-scm.com/docs/api-trace2 func Parse(ctx context.Context, reader io.Reader) (*Trace, error) { span, _ := tracing.StartSpanIfHasParent(ctx, "trace2.parse", nil) - defer span.Finish() + defer span.End() decoder := json.NewDecoder(reader) p := &parser{decoder: decoder} diff --git a/internal/git/trace2hooks/tracingexporter.go b/internal/git/trace2hooks/tracingexporter.go index a2bf5de8a0d..f14f43817cc 100644 --- a/internal/git/trace2hooks/tracingexporter.go +++ b/internal/git/trace2hooks/tracingexporter.go @@ -4,9 +4,10 @@ import ( "context" "fmt" - "github.com/opentracing/opentracing-go" "gitlab.com/gitlab-org/gitaly/v18/internal/git/trace2" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" + "go.opentelemetry.io/otel/attribute" + oteltrace "go.opentelemetry.io/otel/trace" ) // NewTracingExporter initializes TracingExporter, which is a hook to convert Trace2 events to @@ -35,13 +36,13 @@ func (t *TracingExporter) Handle(rootCtx context.Context, trace *trace2.Trace) e } spanName := fmt.Sprintf("git:%s", trace.Name) - span, ctx := tracing.StartSpanIfHasParent(ctx, spanName, nil, opentracing.StartTime(trace.StartTime)) - span.SetTag("thread", trace.Thread) - span.SetTag("childID", trace.ChildID) + span, ctx := tracing.StartSpanIfHasParent(ctx, spanName, nil, oteltrace.WithTimestamp(trace.StartTime)) + span.SetAttributes(attribute.String("thread", trace.Thread)) + span.SetAttributes(attribute.String("childID", trace.ChildID)) for key, value := range trace.Metadata { - span.SetTag(key, value) + span.SetAttributes(attribute.String(key, value)) } - span.FinishWithOptions(opentracing.FinishOptions{FinishTime: trace.FinishTime}) + span.End(oteltrace.WithTimestamp(trace.FinishTime)) return ctx }) diff --git a/internal/git/trace2hooks/tracingexporter_test.go b/internal/git/trace2hooks/tracingexporter_test.go index d25662ff1f3..c74c4346d98 100644 --- a/internal/git/trace2hooks/tracingexporter_test.go +++ b/internal/git/trace2hooks/tracingexporter_test.go @@ -9,11 +9,15 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/git/trace2" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + oteltrace "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) func TestTracingExporter_Handle(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer testhelper.MustClose(t, reporter) // Pin a timestamp for trace tree generation below. This way enables asserting the time // frames of spans correctly. @@ -24,135 +28,151 @@ func TestTracingExporter_Handle(t *testing.T) { for _, tc := range []struct { desc string - setup func(*testing.T) (context.Context, *trace2.Trace) - expectedSpans []*testhelper.Span + setup func(*testing.T) (context.Context, *trace2.Trace, oteltrace.Span) + expectedSpans tracetest.SpanStubs }{ { desc: "empty trace", - setup: func(t *testing.T) (context.Context, *trace2.Trace) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) - return ctx, nil + setup: func(t *testing.T) (context.Context, *trace2.Trace, oteltrace.Span) { + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + return ctx, nil, span }, - expectedSpans: nil, + expectedSpans: tracetest.SpanStubs{}, }, { desc: "receives trace consisting of root only", - setup: func(t *testing.T) (context.Context, *trace2.Trace) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + setup: func(t *testing.T) (context.Context, *trace2.Trace, oteltrace.Span) { + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) return ctx, &trace2.Trace{ Thread: "main", Name: "root", StartTime: current, FinishTime: time.Time{}, - } + }, span }, - expectedSpans: nil, + expectedSpans: tracetest.SpanStubs{}, }, { desc: "receives a complete trace", - setup: func(t *testing.T) (context.Context, *trace2.Trace) { - _, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) - - return ctx, exampleTrace + setup: func(t *testing.T) (context.Context, *trace2.Trace, oteltrace.Span) { + span, ctx := tracing.StartSpan(testhelper.Context(t), "root", nil) + return ctx, exampleTrace, span }, - expectedSpans: []*testhelper.Span{ + expectedSpans: []tracetest.SpanStub{ { - Operation: "git:version", + Name: "git:version", StartTime: current, - Duration: 1 * time.Second, - Tags: map[string]string{ - "childID": "", - "thread": "main", - "exe": "2.42.0", + EndTime: current.Add(1 * time.Second), + Attributes: []attribute.KeyValue{ + attribute.String("childID", ""), + attribute.String("thread", "main"), + attribute.String("exe", "2.42.0"), }, }, { - Operation: "git:start", + Name: "git:start", StartTime: current.Add(1 * time.Second), - Duration: 1 * time.Second, - Tags: map[string]string{ - "argv": "git fetch origin master", - "childID": "", - "thread": "main", + EndTime: current.Add(2 * time.Second), + Attributes: []attribute.KeyValue{ + attribute.String("argv", "git fetch origin master"), + attribute.String("childID", ""), + attribute.String("thread", "main"), }, }, { - Operation: "git:def_repo", + Name: "git:def_repo", StartTime: current.Add(2 * time.Second), - Duration: 1 * time.Second, - Tags: map[string]string{ - "childID": "", - "thread": "main", - "worktree": "/Users/userx123/Documents/gitlab-development-kit", + EndTime: current.Add(3 * time.Second), + Attributes: []attribute.KeyValue{ + attribute.String("childID", ""), + attribute.String("thread", "main"), + attribute.String("worktree", "/Users/userx123/Documents/gitlab-development-kit"), }, }, { - Operation: "git:index:do_read_index", + Name: "git:index:do_read_index", StartTime: current.Add(3 * time.Second), - Duration: 3 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "", - "thread": "main", + EndTime: current.Add(6 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", ""), + attribute.String("thread", "main"), }, }, { - Operation: "git:cache_tree:read", + Name: "git:cache_tree:read", StartTime: current.Add(3 * time.Second), - Duration: 1 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "0", - "thread": "main", + EndTime: current.Add(4 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", "0"), + attribute.String("thread", "main"), }, }, { - Operation: "git:data:index:read/version", + Name: "git:data:index:read/version", StartTime: current.Add(4 * time.Second), - Duration: 1 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "0", - "thread": "main", - "data": "2", + EndTime: current.Add(5 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", "0"), + attribute.String("thread", "main"), + attribute.String("data", "2"), }, }, { - Operation: "git:data:index:read/cache_nr", + Name: "git:data:index:read/cache_nr", StartTime: current.Add(5 * time.Second), - Duration: 1 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "0", - "thread": "main", - "data": "1500", + EndTime: current.Add(6 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", "0"), + attribute.String("thread", "main"), + attribute.String("data", "1500"), }, }, { - Operation: "git:submodule:parallel/fetch", + Name: "git:submodule:parallel/fetch", StartTime: current.Add(6 * time.Second), - Duration: 1 * time.Second, // 3 children - Tags: map[string]string{ - "childID": "", - "thread": "main", + EndTime: current.Add(7 * time.Second), // 3 children + Attributes: []attribute.KeyValue{ + attribute.String("childID", ""), + attribute.String("thread", "main"), }, }, }, }, { desc: "receives a complete trace but tracing is not initialized", - setup: func(t *testing.T) (context.Context, *trace2.Trace) { - return testhelper.Context(t), exampleTrace + setup: func(t *testing.T) (context.Context, *trace2.Trace, oteltrace.Span) { + return testhelper.Context(t), exampleTrace, noop.Span{} }, expectedSpans: nil, }, } { t.Run(tc.desc, func(t *testing.T) { reporter.Reset() - ctx, trace := tc.setup(t) + ctx, trace, span := tc.setup(t) + defer span.End() exporter := NewTracingExporter() err := exporter.Handle(ctx, trace) require.NoError(t, err) - spans := testhelper.ReportedSpans(t, reporter) - require.Equal(t, tc.expectedSpans, spans) + recordedSpans := reporter.GetSpans() + + for idx, expectedSpan := range tc.expectedSpans { + recordedSpan := recordedSpans[idx] + require.Equal(t, expectedSpan.Name, recordedSpan.Name) + + recordedAttributes := attributesToMap(recordedSpan.Attributes) + expectedAttributes := attributesToMap(expectedSpan.Attributes) + require.Equal(t, recordedAttributes, expectedAttributes) + } }) } } + +func attributesToMap(attributes []attribute.KeyValue) map[string]string { + res := make(map[string]string) + for _, attr := range attributes { + res[string(attr.Key)] = attr.Value.AsString() + } + return res +} diff --git a/internal/gitaly/server/server.go b/internal/gitaly/server/server.go index 64b5c953b6f..2a388e33704 100644 --- a/internal/gitaly/server/server.go +++ b/internal/gitaly/server/server.go @@ -25,8 +25,10 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/grpc/protoregistry" gitalylog "gitlab.com/gitlab-org/gitaly/v18/internal/log" "gitlab.com/gitlab-org/gitaly/v18/internal/structerr" + "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc" - grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" @@ -162,7 +164,6 @@ func (s *GitalyServerFactory) New(external, secure bool, opts ...Option) (*grpc. } streamServerInterceptors = append(streamServerInterceptors, - grpctracing.StreamServerTracingInterceptor(), cache.StreamInvalidator(s.cacheInvalidator, protoregistry.GitalyProtoPreregistered, s.logger), // Panic handler should remain last so that application panics will be // converted to errors and logged @@ -170,7 +171,6 @@ func (s *GitalyServerFactory) New(external, secure bool, opts ...Option) (*grpc. ) unaryServerInterceptors = append(unaryServerInterceptors, - grpctracing.UnaryServerTracingInterceptor(), cache.UnaryInvalidator(s.cacheInvalidator, protoregistry.GitalyProtoPreregistered, s.logger), // Panic handler should remain last so that application panics will be // converted to errors and logged @@ -204,6 +204,9 @@ func (s *GitalyServerFactory) New(external, secure bool, opts ...Option) (*grpc. } serverOptions := []grpc.ServerOption{ + grpc.StatsHandler(tracing.NewGRPCServerStatsHandler( + otelgrpc.WithTracerProvider(otel.GetTracerProvider()), + )), grpc.StatsHandler(loghandler.PerRPCLogHandler{ Underlying: &grpcstats.PayloadBytes{}, FieldProducers: []loghandler.FieldsProducer{grpcstats.FieldsProducer}, diff --git a/internal/gitaly/service/repository/license.go b/internal/gitaly/service/repository/license.go index 39c1524076b..b65a12f9059 100644 --- a/internal/gitaly/service/repository/license.go +++ b/internal/gitaly/service/repository/license.go @@ -72,7 +72,7 @@ func (s *server) FindLicense(ctx context.Context, req *gitalypb.FindLicenseReque func findLicense(ctx context.Context, repo *localrepo.Repo, commitID git.ObjectID) (*gitalypb.FindLicenseResponse, error) { span, ctx := tracing.StartSpanIfHasParent(ctx, "repository.findLicense", nil) - defer span.Finish() + defer span.End() repoFiler := &gitFiler{ctx: ctx, repo: repo, treeishID: commitID} detectedLicenses, err := licensedb.Detect(repoFiler) diff --git a/internal/gitaly/storage/storagemgr/middleware.go b/internal/gitaly/storage/storagemgr/middleware.go index 88417e7b668..f9e382367d4 100644 --- a/internal/gitaly/storage/storagemgr/middleware.go +++ b/internal/gitaly/storage/storagemgr/middleware.go @@ -209,7 +209,7 @@ func nonTransactionalRequest(ctx context.Context, firstMessage proto.Message) tr // transaction. The returned values are valid even if the request should not run transactionally. func transactionalizeRequest(ctx context.Context, logger log.Logger, txRegistry *TransactionRegistry, node storage.Node, locator storage.Locator, methodInfo protoregistry.MethodInfo, req proto.Message) (_ transactionalizedRequest, returnedErr error) { span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.transactionalizeRequest", nil) - defer span.Finish() + defer span.End() switch methodInfo.Scope { case protoregistry.ScopeRepository: diff --git a/internal/gitaly/storage/storagemgr/partition/transaction_manager.go b/internal/gitaly/storage/storagemgr/partition/transaction_manager.go index 8056d30a85b..bcf40640781 100644 --- a/internal/gitaly/storage/storagemgr/partition/transaction_manager.go +++ b/internal/gitaly/storage/storagemgr/partition/transaction_manager.go @@ -42,6 +42,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" "gitlab.com/gitlab-org/labkit/correlation" + "go.opentelemetry.io/otel/attribute" "golang.org/x/sync/errgroup" "google.golang.org/protobuf/proto" ) @@ -248,9 +249,9 @@ func (mgr *TransactionManager) Begin(ctx context.Context, opts storage.BeginOpti } span, _ := tracing.StartSpanIfHasParent(ctx, "transaction.Begin", nil) - span.SetTag("write", opts.Write) - span.SetTag("relativePath", relativePath) - defer span.Finish() + span.SetAttributes(attribute.Bool("write", opts.Write)) + span.SetAttributes(attribute.String("relativePath", relativePath)) + defer span.End() mgr.mutex.Lock() @@ -275,7 +276,7 @@ func (mgr *TransactionManager) Begin(ctx context.Context, opts storage.BeginOpti mgr.mutex.Unlock() - span.SetTag("snapshotLSN", txn.snapshotLSN) + span.SetAttributes(attribute.String("snapshotLSN", txn.snapshotLSN.String())) txn.finish = func(admitted bool) error { defer trace.StartRegion(ctx, "finish transaction").End() @@ -1078,7 +1079,7 @@ type resultChannel chan commitResult // commit queues the transaction for processing and returns once the result has been determined. func (mgr *TransactionManager) commit(ctx context.Context, transaction *Transaction) (storage.LSN, error) { span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.Commit", nil) - defer span.Finish() + defer span.End() transaction.result = make(resultChannel, 1) @@ -1229,7 +1230,7 @@ func (txn *Transaction) referenceUpdatesToProto() []*gitalypb.LogEntry_Reference // complete state and stages it into the transaction for committing. func (mgr *TransactionManager) stageRepositoryCreation(ctx context.Context, transaction *Transaction) error { span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.stageRepositoryCreation", nil) - defer span.Finish() + defer span.End() objectHash, err := transaction.snapshotRepository.ObjectHash(ctx) if err != nil { @@ -1268,7 +1269,7 @@ func (mgr *TransactionManager) setupStagingRepository(ctx context.Context, trans defer trace.StartRegion(ctx, "setupStagingRepository").End() span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.setupStagingRepository", nil) - defer span.Finish() + defer span.End() if transaction.stagingSnapshot != nil { return nil, errors.New("staging snapshot already setup") @@ -1318,7 +1319,7 @@ func (mgr *TransactionManager) packObjects(ctx context.Context, transaction *Tra } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.packObjects", nil) - defer span.Finish() + defer span.End() // We want to only pack the objects that are present in the quarantine as they are potentially // new. Disable the alternate, which is the repository's original object directory, so that we'll @@ -1759,7 +1760,7 @@ func (mgr *TransactionManager) processTransaction(ctx context.Context) (returned } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.processTransaction", nil) - defer span.Finish() + defer span.End() transaction.result <- func() commitResult { var zeroOID git.ObjectID diff --git a/internal/gitaly/storage/storagemgr/partition/transaction_manager_housekeeping.go b/internal/gitaly/storage/storagemgr/partition/transaction_manager_housekeeping.go index e1d130b8d4d..94a7706fc37 100644 --- a/internal/gitaly/storage/storagemgr/partition/transaction_manager_housekeeping.go +++ b/internal/gitaly/storage/storagemgr/partition/transaction_manager_housekeeping.go @@ -122,7 +122,7 @@ func (mgr *TransactionManager) prepareHousekeeping(ctx context.Context, transact } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareHousekeeping", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("total", "prepare") defer finishTimer() @@ -155,7 +155,7 @@ func (mgr *TransactionManager) preparePackRefs(ctx context.Context, transaction } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.preparePackRefs", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("pack-refs", "prepare") defer finishTimer() @@ -204,7 +204,7 @@ func (mgr *TransactionManager) prepareRepacking(ctx context.Context, transaction } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareRepacking", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("repack", "prepare") defer finishTimer() @@ -392,7 +392,7 @@ func (mgr *TransactionManager) prepareCommitGraphs(ctx context.Context, transact } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareCommitGraphs", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("commit-graph", "prepare") defer finishTimer() @@ -471,7 +471,7 @@ func (mgr *TransactionManager) verifyHousekeeping(ctx context.Context, transacti defer trace.StartRegion(ctx, "verifyHousekeeping").End() span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.verifyHousekeeping", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("total", "verify") defer finishTimer() @@ -529,7 +529,7 @@ func (mgr *TransactionManager) verifyPackRefs(ctx context.Context, transaction * } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.verifyPackRefs", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("pack-refs", "verify") defer finishTimer() @@ -576,7 +576,7 @@ func (mgr *TransactionManager) verifyRepacking(ctx context.Context, transaction } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.verifyRepacking", nil) - defer span.Finish() + defer span.End() finishTimer := mgr.metrics.housekeeping.ReportTaskLatency("repack", "verify") defer finishTimer() @@ -838,7 +838,7 @@ func (mgr *TransactionManager) prepareOffloading(ctx context.Context, transactio } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareOffloading", nil) - defer span.Finish() + defer span.End() // Loading configurations for offloading cfg := transaction.runHousekeeping.runOffloading.config @@ -977,7 +977,7 @@ func (mgr *TransactionManager) prepareRehydrating(ctx context.Context, transacti } span, ctx := tracing.StartSpanIfHasParent(ctx, "transaction.prepareRehydrating", nil) - defer span.Finish() + defer span.End() workingRepository := mgr.repositoryFactory.Build(transaction.snapshot.RelativePath(transaction.relativePath)) // workingRepoPath is the current repository path which we are performing operations on. diff --git a/internal/grpc/client/dial.go b/internal/grpc/client/dial.go index adb258b9478..21d52f521fe 100644 --- a/internal/grpc/client/dial.go +++ b/internal/grpc/client/dial.go @@ -12,7 +12,6 @@ import ( gitalyx509 "gitlab.com/gitlab-org/gitaly/v18/internal/x509" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" grpccorrelation "gitlab.com/gitlab-org/labkit/correlation/grpc" - grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" @@ -98,7 +97,7 @@ func WithTransportCredentials(creds credentials.TransportCredentials) DialOption // New creates a dormant connection to a Gitaly node serving at the given address. New is used by the public 'client' // package and the expected behavior is mostly documented there. -func New(ctx context.Context, rawAddress string, opts ...DialOption) (*grpc.ClientConn, error) { +func New(_ context.Context, rawAddress string, opts ...DialOption) (*grpc.ClientConn, error) { var dialCfg dialConfig for _, opt := range opts { opt(&dialCfg) @@ -213,7 +212,6 @@ func New(ctx context.Context, rawAddress string, opts ...DialOption) (*grpc.Clie // StreamInterceptor returns the stream interceptors that should be configured for a client. func StreamInterceptor() grpc.DialOption { return grpc.WithChainStreamInterceptor( - grpctracing.StreamClientTracingInterceptor(), grpccorrelation.StreamClientCorrelationInterceptor(), ) } @@ -221,7 +219,6 @@ func StreamInterceptor() grpc.DialOption { // UnaryInterceptor returns the unary interceptors that should be configured for a client. func UnaryInterceptor() grpc.DialOption { return grpc.WithChainUnaryInterceptor( - grpctracing.UnaryClientTracingInterceptor(), grpccorrelation.UnaryClientCorrelationInterceptor(), ) } diff --git a/internal/grpc/sidechannel/sidechannel.go b/internal/grpc/sidechannel/sidechannel.go index a9b69dcfd97..0ef683e9717 100644 --- a/internal/grpc/sidechannel/sidechannel.go +++ b/internal/grpc/sidechannel/sidechannel.go @@ -39,7 +39,7 @@ type RetryableError struct{ error } // extracted from the current peer connection. func OpenSidechannel(ctx context.Context) (_ *ServerConn, err error) { span, ctx := tracing.StartSpanIfHasParent(ctx, "sidechannel.OpenSidechannel", nil) - defer span.Finish() + defer span.End() md, ok := metadata.FromIncomingContext(ctx) if !ok { diff --git a/internal/limiter/concurrency_limiter.go b/internal/limiter/concurrency_limiter.go index 4f52a50d6e2..28b78e4074b 100644 --- a/internal/limiter/concurrency_limiter.go +++ b/internal/limiter/concurrency_limiter.go @@ -11,6 +11,7 @@ import ( "gitlab.com/gitlab-org/gitaly/v18/internal/structerr" "gitlab.com/gitlab-org/gitaly/v18/internal/tracing" "gitlab.com/gitlab-org/gitaly/v18/proto/go/gitalypb" + "go.opentelemetry.io/otel/attribute" "google.golang.org/protobuf/types/known/durationpb" ) @@ -197,9 +198,11 @@ func (c *ConcurrencyLimiter) Limit(ctx context.Context, limitingKey string, f Li span, ctx := tracing.StartSpanIfHasParent( ctx, "limiter.ConcurrencyLimiter.Limit", - tracing.Tags{"key": limitingKey}, + []attribute.KeyValue{ + attribute.String("key", limitingKey), + }, ) - defer span.Finish() + defer span.End() if c.currentLimit() <= 0 { return f() diff --git a/internal/testhelper/tracing.go b/internal/testhelper/tracing.go index ea5c279a4e9..0e739a591d7 100644 --- a/internal/testhelper/tracing.go +++ b/internal/testhelper/tracing.go @@ -1,17 +1,21 @@ package testhelper import ( - "fmt" + "context" "testing" - "time" - "github.com/opentracing/opentracing-go" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + oteltracingsdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" + "go.opentelemetry.io/otel/trace" ) type stubTracingReporterConfig struct { - sampler jaeger.Sampler + sampler oteltracingsdk.Sampler } // StubTracingReporterOption is a function that modifies the config of stubbed tracing reporter @@ -20,67 +24,94 @@ type StubTracingReporterOption func(*stubTracingReporterConfig) // NeverSampled is an option that makes the stubbed tracer never sample spans func NeverSampled() StubTracingReporterOption { return func(conf *stubTracingReporterConfig) { - conf.sampler = jaeger.NewConstSampler(false) + conf.sampler = oteltracingsdk.NeverSample() } } -// StubTracingReporter stubs the distributed tracing's global tracer. It returns a reporter that -// records all generated spans along the way. The data is cleaned up afterward after the test is -// done. As there is only one global tracer, this stub is not safe to run in parallel. -func StubTracingReporter(t *testing.T, opts ...StubTracingReporterOption) (*jaeger.InMemoryReporter, func()) { +// StubTracingReporter is a tracing reporter to be sued in tests. +// It uses a memory exporter to save all spans in memory, which +// allows for inspection during tests. +type StubTracingReporter struct { + exporter *tracetest.InMemoryExporter + tp *oteltracingsdk.TracerProvider + old trace.TracerProvider +} + +// NewStubTracingReporter stubs the distributed tracing's global tracer. It returns a reporter that +// records all generated spans along the way. The data is cleaned up afterward after the test is done. +// The `StubTracingReporter` has a `TracerProvider()` method to get access to the tracer provider. +// This allows tests using this stub to run in parallel. +func NewStubTracingReporter(t *testing.T, opts ...StubTracingReporterOption) *StubTracingReporter { conf := &stubTracingReporterConfig{ - jaeger.NewConstSampler(true), + oteltracingsdk.AlwaysSample(), } for _, opt := range opts { opt(conf) } - reporter := jaeger.NewInMemoryReporter() - tracer, tracerCloser := jaeger.NewTracer("", conf.sampler, reporter) + res, err := resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName(t.Name()), + semconv.ServiceVersion("1.0.0"), + ), + ) + require.NoError(t, err) + + exporter := tracetest.NewInMemoryExporter() - old := opentracing.GlobalTracer() - opentracing.SetGlobalTracer(tracer) + tp := oteltracingsdk.NewTracerProvider( + oteltracingsdk.WithSyncer(exporter), + oteltracingsdk.WithSampler(conf.sampler), + oteltracingsdk.WithResource(res), + ) - return reporter, func() { - MustClose(t, tracerCloser) - opentracing.SetGlobalTracer(old) + old := otel.GetTracerProvider() + + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}), + ) + + return &StubTracingReporter{ + exporter: exporter, + tp: tp, + old: old, } } -// Span is a struct that provides a more test-friendly way to assert distributed tracing spans. -type Span struct { - // Operation is the name of the operation being traced. - Operation string - // StartTime is the point in time when the span started. - StartTime time.Time - // Duration captures the elapsed time of the operation traced by the span. - Duration time.Duration - // Tags is a map that contains key-value pairs of stringified tags associated with the span. - Tags map[string]string +// TracerProvider returns the tracer provider associated with this reporter +func (e *StubTracingReporter) TracerProvider() trace.TracerProvider { + return e.tp } -// ReportedSpans function converts the spans that were captured by the stubbed reporter into an -// assertable data structure. Initially, the collected traces are represented using opentracing's -// Span interface. However, this interface is not suitable for testing purposes. Therefore, we must -// cast them to a Jaeger Span, which contains a lot of information that is not relevant to testing. -// The new span struct that is created contains only essential and safe-for-testing fields -func ReportedSpans(t *testing.T, reporter *jaeger.InMemoryReporter) []*Span { - var reportedSpans []*Span - for _, span := range reporter.GetSpans() { - jaegerSpan, ok := span.(*jaeger.Span) - require.Truef(t, ok, "stubbed span must be a Jaeger span") - - reportedSpan := &Span{ - Operation: jaegerSpan.OperationName(), - StartTime: jaegerSpan.StartTime(), - Duration: jaegerSpan.Duration(), - Tags: map[string]string{}, - } +// GetSpans returns all spans saved in memory +func (e *StubTracingReporter) GetSpans() tracetest.SpanStubs { + return e.exporter.GetSpans() +} + +// Reset empties the memory buffer holding spans. +// This is useful to reuse the same Reporter. +func (e *StubTracingReporter) Reset() { + e.exporter.Reset() +} + +// Close closes the underlying tracer provider +func (e *StubTracingReporter) Close() error { + defer func() { + otel.SetTracerProvider(e.old) + }() + return e.tp.Shutdown(context.Background()) +} - for key, value := range jaegerSpan.Tags() { - reportedSpan.Tags[key] = fmt.Sprintf("%s", value) +// GetSpanByName returns a span by its name +func (e *StubTracingReporter) GetSpanByName(name string) tracetest.SpanStub { + for _, recordedSpan := range e.GetSpans() { + if recordedSpan.Name == name { + return recordedSpan } - reportedSpans = append(reportedSpans, reportedSpan) } - return reportedSpans + return tracetest.SpanStub{} } diff --git a/internal/tracing/configuration.go b/internal/tracing/configuration.go index d3b3f8f05eb..5d7bef8107a 100644 --- a/internal/tracing/configuration.go +++ b/internal/tracing/configuration.go @@ -200,7 +200,7 @@ func parseConnectionString(connStr string) (cfg tracingConfig, err error) { if f > 1.0 || f < 0.0 { return tracingConfig{}, - fmt.Errorf("%s is '%v' but must be a float value between 0.0 and 1.0", optSamplerParameter, samplerFloatValue) + fmt.Errorf("%s is '%v' but must be a float value between 0.0 and 1.0", optSamplerParameter, f) } samplerFloatValue = clampValue(f, 0.0, 1.0) diff --git a/internal/tracing/middlewares.go b/internal/tracing/middlewares.go index a9ee2206489..ba050a4ceeb 100644 --- a/internal/tracing/middlewares.go +++ b/internal/tracing/middlewares.go @@ -12,6 +12,15 @@ func NewGRPCServerStatsHandler(opts ...otelgrpc.Option) stats.Handler { defaultOpts := []otelgrpc.Option{ otelgrpc.WithPropagators(defaultPropagator), } - return otelgrpc.NewServerHandler(append(defaultOpts, opts...)...) } + +// NewGRPCClientStatsHandler is an OTEL instrumented middleware for gRPC client. It returns a stats.Handler as +// recommended by the OTEL project after deprecating the UnaryClientInterceptor. +// See: https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc@v0.54.0#UnaryServerInterceptor +func NewGRPCClientStatsHandler(opts ...otelgrpc.Option) stats.Handler { + defaultOpts := []otelgrpc.Option{ + otelgrpc.WithPropagators(defaultPropagator), + } + return otelgrpc.NewClientHandler(append(defaultOpts, opts...)...) +} diff --git a/internal/tracing/noop.go b/internal/tracing/noop.go deleted file mode 100644 index 38d96c76e42..00000000000 --- a/internal/tracing/noop.go +++ /dev/null @@ -1,74 +0,0 @@ -package tracing - -import ( - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/log" -) - -// NoopSpan is a dummy span implementing opentracing.Span interface. All data setting functions do -// nothing. Data getting functions return other dummy objects. Spans of this kind are not recorded -// later. -type NoopSpan struct{} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) Finish() {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) FinishWithOptions(_ opentracing.FinishOptions) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) Context() opentracing.SpanContext { return NoopSpanContext{} } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) LogFields(...log.Field) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) SetOperationName(string) opentracing.Span { return s } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) Log(opentracing.LogData) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) SetTag(string, interface{}) opentracing.Span { return s } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) LogKV(...interface{}) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) SetBaggageItem(string, string) opentracing.Span { return s } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) BaggageItem(string) string { return "" } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) Tracer() opentracing.Tracer { return &NoopTracer{} } - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) LogEvent(string) {} - -//nolint:revive // This is unintentionally missing documentation. -func (s *NoopSpan) LogEventWithPayload(string, interface{}) {} - -// NoopSpanContext is a dummy context returned by NoopSpan -type NoopSpanContext struct{} - -//nolint:revive // This is unintentionally missing documentation. -func (n NoopSpanContext) ForeachBaggageItem(func(k string, v string) bool) {} - -// NoopTracer is a dummy tracer returned by NoopSpan -type NoopTracer struct{} - -//nolint:revive // This is unintentionally missing documentation. -func (n NoopTracer) StartSpan(string, ...opentracing.StartSpanOption) opentracing.Span { - return &NoopSpan{} -} - -//nolint:revive // This is unintentionally missing documentation. -func (n NoopTracer) Inject(opentracing.SpanContext, interface{}, interface{}) error { - return nil -} - -//nolint:revive // This is unintentionally missing documentation. -func (n NoopTracer) Extract(interface{}, interface{}) (opentracing.SpanContext, error) { - return &NoopSpanContext{}, nil -} diff --git a/internal/tracing/passthrough.go b/internal/tracing/passthrough.go index 39af2408afa..77cec848aa4 100644 --- a/internal/tracing/passthrough.go +++ b/internal/tracing/passthrough.go @@ -2,53 +2,76 @@ package tracing import ( "context" + "errors" "strings" - grpcmwmetadata "github.com/grpc-ecosystem/go-grpc-middleware/v2/metadata" - "github.com/opentracing/opentracing-go" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) -// ExtractSpanContextFromEnv extracts a SpanContext from the environment variable list. The caller -// usually passes the result of os.Environ() into this method. -func ExtractSpanContextFromEnv(envs []string) (opentracing.SpanContext, error) { - envMap := environAsMap(envs) - return opentracing.GlobalTracer().Extract( - opentracing.TextMap, - opentracing.TextMapCarrier(envMap), - ) +var errNoSpanContextInEnv = errors.New("no span context found in environment variables") + +// PropagateFromEnv propagates the SpanContext extracted from environment variables +// into a new context that can later be used to create child spans. +func PropagateFromEnv(envs []string) (context.Context, error) { + // Create carrier from environment variables. A carrier is an object that holds data + // and to which data can be written, and from which data can be read. + carrier := propagation.MapCarrier(environAsMap(envs)) + + // Create a TextMap propagator, that can read from and write to any TextMap carrier such as a MapCarrier. + // This propagator is created with the TraceContext and Baggage propagator, which means that it is + // configured to either read trace context and baggage from a context and write it into a carrier, or read + // baggage and trace context from a carrier to inject it into a new span context. + propagator := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) + + // Start with an empty context + emptyCtx := context.Background() + + // Using the empty context as parent, create a new context in which to inject the span context from the + // carrier (if any). + spanCtx := propagator.Extract(emptyCtx, carrier) + + // If a span context has been successfully injected into `spanCtx`, return it. + if trace.SpanContextFromContext(spanCtx).IsValid() { + return spanCtx, nil + } + + // Else, return the empty context with no span context whatsoever + return emptyCtx, errNoSpanContextInEnv } -// UnaryPassthroughInterceptor is a client gRPC unary interceptor that rewrites a span context into -// the outgoing metadata of the call. It is useful for intermediate systems who don't want to -// start new spans. -func UnaryPassthroughInterceptor(spanContext opentracing.SpanContext) grpc.UnaryClientInterceptor { - return func(parentCtx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { - ctxWithMetadata := injectSpanContext(parentCtx, spanContext) +// UnaryPassthroughInterceptor is a client gRPC unary interceptor that injects a span context into +// the outgoing context of the call. It is useful to propagate span context between processes that do +// not have automatic span propagation between them. +func UnaryPassthroughInterceptor(contextToInject context.Context) grpc.UnaryClientInterceptor { + return func(requestCtx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + ctxWithMetadata := injectSpanContextIntoContext(requestCtx, contextToInject) return invoker(ctxWithMetadata, method, req, reply, cc, opts...) } } -// StreamPassthroughInterceptor is equivalent to UnaryPassthroughInterceptor, but for streaming -// gRPC calls. -func StreamPassthroughInterceptor(spanContext opentracing.SpanContext) grpc.StreamClientInterceptor { - return func(parentCtx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { - ctxWithMetadata := injectSpanContext(parentCtx, spanContext) +// StreamPassthroughInterceptor is equivalent to UnaryPassthroughInterceptor, but for streaming gRPC calls. +func StreamPassthroughInterceptor(contextToInject context.Context) grpc.StreamClientInterceptor { + return func(requestCtx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + ctxWithMetadata := injectSpanContextIntoContext(requestCtx, contextToInject) return streamer(ctxWithMetadata, desc, cc, method, opts...) } } -func injectSpanContext(parentCtx context.Context, spanContext opentracing.SpanContext) context.Context { - tracer := opentracing.GlobalTracer() - md := grpcmwmetadata.ExtractOutgoing(parentCtx).Clone() - if err := tracer.Inject(spanContext, opentracing.HTTPHeaders, grpcMetadataCarrier(md)); err != nil { - return parentCtx - } - ctxWithMetadata := md.ToOutgoing(parentCtx) - return ctxWithMetadata +// injectSpanContextIntoContext inject the span context included in `contextToInject` into `ctx`. +// Because context are immutable objects, the returned context is a new context with `ctx` as its parent +// and includes the span context extracted from `contextToInject`. +func injectSpanContextIntoContext(ctx context.Context, contextToInject context.Context) context.Context { + grpcMetaData, _ := metadata.FromOutgoingContext(ctx) + defaultPropagator.Inject(contextToInject, grpcMetadataCarrier(grpcMetaData)) + return metadata.NewOutgoingContext(ctx, grpcMetaData) } +// environAsMap takes a list of environment variable in the format `key=value` +// and splits each value using the `=` sign such as to return a map with each +// key associated with its value. func environAsMap(env []string) map[string]string { envMap := make(map[string]string, len(env)) for _, v := range env { @@ -58,12 +81,16 @@ func environAsMap(env []string) map[string]string { return envMap } -// metadataTextMap is a wrapper for gRPC's metadata.MD. It implements opentracing.TextMapWriter, -// which is to set opentracing-related fields. In this use case, the passthrough interceptors touch -// span identify and maybe some luggage or tag fields. This implementation is good-enough for such -// fields. gRPC header name format is: -// > Header-Name → 1*( %x30-39 / %x61-7A / "_" / "-" / ".") ; 0-9 a-z _ - . -// > Source: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md +// grpcMetadataCarrier implements propagation.TextMapCarrier. The purpose of this +// custom implementation is to convert all metadata keys into lowercase. The default +// OTEL implementation of HeaderCarrier converts all keys into HTTP Headers using the +// canonical form. See: +// * https://github.com/open-telemetry/opentelemetry-go/blob/main/propagation/propagation.go#L92 +// * https://github.com/golang/go/blob/master/src/net/http/header.go#L40 +// +// However, in gRPC, only lowerkey metadata keys are accepted. The character +// set is `[0-9-a-z-_.]`. See: +// * https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md type grpcMetadataCarrier metadata.MD func (g grpcMetadataCarrier) Get(key string) string { diff --git a/internal/tracing/passthrough_test.go b/internal/tracing/passthrough_test.go index 08db259688e..550f26513f4 100644 --- a/internal/tracing/passthrough_test.go +++ b/internal/tracing/passthrough_test.go @@ -7,120 +7,152 @@ import ( "net" "testing" - "github.com/opentracing/opentracing-go" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" "gitlab.com/gitlab-org/gitaly/v18/internal/grpc/client" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" - grpctracing "gitlab.com/gitlab-org/labkit/tracing/grpc" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/baggage" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "google.golang.org/grpc/interop/grpc_testing" ) func TestExtractSpanContextFromEnv(t *testing.T) { - _, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer testhelper.MustClose(t, reporter) - injectedSpan := opentracing.StartSpan("test", opentracing.Tag{Key: "do-not-carry", Value: "value"}) - injectedSpan.SetBaggageItem("hi", "hello") + ctx := context.Background() - jaegerInjectedSpan := injectedSpan.(*jaeger.Span) - jaegerInjectedSpanContext := jaegerInjectedSpan.SpanContext() + tracer := reporter.TracerProvider().Tracer(t.Name()) + initSpanCtx, span := tracer.Start(ctx, "test", + trace.WithAttributes(attribute.String("do-not-carry", "value")), + ) + defer span.End() + + testBaggage, err := baggage.NewMember("hi", "hello") + require.NoError(t, err) + + bg, err := baggage.New(testBaggage) + require.NoError(t, err) + + // Set baggage into a new immutable context + initSpanCtx = baggage.ContextWithBaggage(initSpanCtx, bg) - createSpanContext := func() []string { - env := envMap{} - err := opentracing.GlobalTracer().Inject(injectedSpan.Context(), opentracing.TextMap, env) - require.NoError(t, err) - return env.toSlice() + createSpanContextAsEnv := func() []string { + envs := map[string]string{} + carrier := propagation.MapCarrier(envs) + otel.GetTextMapPropagator().Inject(initSpanCtx, carrier) + return envMapToSlice(envs) } tests := []struct { - desc string - envs []string - expectedContext opentracing.SpanContext - expectedError string + desc string + envs []string + expectedError error }{ { desc: "empty environment map", envs: []string{}, - expectedError: "opentracing: SpanContext not found in Extract carrier", + expectedError: errNoSpanContextInEnv, }, { desc: "irrelevant environment map", envs: []string{"SOME_THING=A", "SOMETHING_ELSE=B"}, - expectedError: "opentracing: SpanContext not found in Extract carrier", + expectedError: errNoSpanContextInEnv, }, { - desc: "environment variable includes span context", - envs: createSpanContext(), + desc: "environment variable includes span context", + envs: createSpanContextAsEnv(), + expectedError: nil, }, } for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { - spanContext, err := ExtractSpanContextFromEnv(tc.envs) - if tc.expectedError != "" { - require.Equal(t, tc.expectedError, err.Error()) + rootSpanCtx, err := PropagateFromEnv(tc.envs) + if err != nil { + require.Equal(t, tc.expectedError, err) } else { require.NoError(t, err) - require.NotNil(t, spanContext) + require.NotNil(t, rootSpanCtx) + + rootSpan := trace.SpanFromContext(rootSpanCtx) + require.True(t, rootSpan.SpanContext().IsValid()) - span := opentracing.StartSpan("test", opentracing.ChildOf(spanContext)) - jaegerSpan := span.(*jaeger.Span) - jaegerSpanContext := jaegerSpan.SpanContext() + // We need a span to verify the propagation and parent-child relationship + // so it is safe to close it just after starting it. + childSpanCtx, childSpan := tracer.Start(rootSpanCtx, tc.desc) + childSpan.End() - require.Equal(t, jaegerInjectedSpanContext.TraceID(), jaegerSpanContext.TraceID()) - require.Equal(t, jaegerInjectedSpanContext.SpanID(), jaegerSpanContext.ParentID()) - require.Equal(t, opentracing.Tags{}, jaegerSpan.Tags()) - require.Equal(t, "hello", jaegerSpan.BaggageItem("hi")) + require.Equal(t, rootSpan.SpanContext().TraceID().String(), childSpan.SpanContext().TraceID().String()) + + // Baggage should have been propagated + childBaggage := baggage.FromContext(childSpanCtx) + require.Equal(t, childBaggage.Member(testBaggage.Key()).Value(), testBaggage.Value()) + + // Attributes on a span are not propagated since they are + // not a span context. + recordedSpan := reporter.GetSpanByName(tc.desc) + require.True(t, recordedSpan.SpanContext.IsValid()) + require.Nil(t, recordedSpan.Attributes) } }) } } func TestUnaryPassthroughInterceptor(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer testhelper.MustClose(t, reporter) + + tracer := reporter.TracerProvider().Tracer(t.Name()) tests := []struct { desc string - setup func(*testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) + setup func(*testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) expectedSpans []string }{ { desc: "empty span context", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - return 0, nil, func() {} + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + emptyCtx := context.Background() + nullTraceID := trace.TraceID{} + return nullTraceID, emptyCtx, func() {} }, expectedSpans: []string{ - "/grpc.testing.TestService/UnaryCall", + "grpc.testing.TestService/UnaryCall", }, }, { - desc: "span context with a simple span", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - span := opentracing.GlobalTracer().StartSpan("root") - return span.(*jaeger.Span).SpanContext().SpanID(), span.Context(), span.Finish + desc: "span context with a single span", + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + initCtx := context.Background() + spanCtx, span := tracer.Start(initCtx, "init") + spanTraceID := span.SpanContext().TraceID() + return spanTraceID, spanCtx, func() { span.End() } }, expectedSpans: []string{ - "/grpc.testing.TestService/UnaryCall", - "root", + "grpc.testing.TestService/UnaryCall", + "init", }, }, { - desc: "span context with a trace chain", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - root := opentracing.GlobalTracer().StartSpan("root") - child := opentracing.GlobalTracer().StartSpan("child", opentracing.ChildOf(root.Context())) - grandChild := opentracing.GlobalTracer().StartSpan("grandChild", opentracing.ChildOf(child.Context())) - - return grandChild.(*jaeger.Span).SpanContext().SpanID(), grandChild.Context(), func() { - grandChild.Finish() - child.Finish() - root.Finish() + desc: "span context with a multi-span trace chain", + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + initCtx := context.Background() + rootCtx, rootSpan := tracer.Start(initCtx, "root") + childCtx, childSpan := tracer.Start(rootCtx, "child") + grandchildCtx, grandchildSpan := tracer.Start(childCtx, "grandChild") + + spanTraceID := grandchildSpan.SpanContext().TraceID() + return spanTraceID, grandchildCtx, func() { + grandchildSpan.End() + childSpan.End() + rootSpan.End() } }, expectedSpans: []string{ - "/grpc.testing.TestService/UnaryCall", + "grpc.testing.TestService/UnaryCall", "grandChild", "child", "root", @@ -131,72 +163,92 @@ func TestUnaryPassthroughInterceptor(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { reporter.Reset() - var parentID jaeger.SpanID + ctx := testhelper.Context(t) + + var traceID trace.TraceID service := &testSvc{ unaryCall: func(ctx context.Context, request *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) { - if span := opentracing.SpanFromContext(ctx); span != nil { - parentID = span.(*jaeger.Span).SpanContext().ParentID() + if span := trace.SpanFromContext(ctx); span.SpanContext().IsValid() { + traceID = span.SpanContext().TraceID() } return &grpc_testing.SimpleResponse{}, nil }, } - expectedParentID, spanContext, finishFunc := tc.setup(t) - client := startFakeGitalyServer(t, service, spanContext) - _, err := client.UnaryCall(testhelper.Context(t), &grpc_testing.SimpleRequest{}) + expectedTraceID, contextToInject, finishFunc := tc.setup(t) + + grpcClient := startFakeGitalyServer(t, contextToInject, service) + _, err := grpcClient.UnaryCall(ctx, &grpc_testing.SimpleRequest{}) require.NoError(t, err) + // In the case where there is no root span to inject into + // the incoming rRPC calls, we cannot know in advance what the + // traceID of the span created by the gRPC middleware will be. + // So in that special case, when we cannot know in advance what + // traceID to expect, we return the empty one. That's why this check + // is needed. + if expectedTraceID.IsValid() { + require.Equal(t, expectedTraceID.String(), traceID.String()) + } + finishFunc() - require.Equal(t, expectedParentID, parentID) - require.Equal(t, tc.expectedSpans, reportedSpans(t, reporter)) + require.Equal(t, tc.expectedSpans, reportedSpanNames(t, reporter)) }) } } func TestStreamPassthroughInterceptor(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() + + tracer := reporter.TracerProvider().Tracer(t.Name()) tests := []struct { desc string - setup func(*testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) + setup func(*testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) expectedSpans []string }{ { desc: "empty span context", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - return 0, nil, func() {} + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + emptyCtx := context.Background() + nullTraceID := trace.TraceID{} + return nullTraceID, emptyCtx, func() {} }, expectedSpans: []string{ - "/grpc.testing.TestService/FullDuplexCall", + "grpc.testing.TestService/FullDuplexCall", }, }, { - desc: "span context with a simple span", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - span := opentracing.GlobalTracer().StartSpan("root") - return span.(*jaeger.Span).SpanContext().SpanID(), span.Context(), span.Finish + desc: "span context with a single span", + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + initCtx := context.Background() + spanCtx, span := tracer.Start(initCtx, "init") + spanTraceID := span.SpanContext().TraceID() + return spanTraceID, spanCtx, func() { span.End() } }, expectedSpans: []string{ - "/grpc.testing.TestService/FullDuplexCall", - "root", + "grpc.testing.TestService/FullDuplexCall", + "init", }, }, { - desc: "span context with a trace chain", - setup: func(t *testing.T) (jaeger.SpanID, opentracing.SpanContext, func()) { - root := opentracing.GlobalTracer().StartSpan("root") - child := opentracing.GlobalTracer().StartSpan("child", opentracing.ChildOf(root.Context())) - grandChild := opentracing.GlobalTracer().StartSpan("grandChild", opentracing.ChildOf(child.Context())) - - return grandChild.(*jaeger.Span).SpanContext().SpanID(), grandChild.Context(), func() { - grandChild.Finish() - child.Finish() - root.Finish() + desc: "span context with a multi-span trace chain", + setup: func(t *testing.T) (traceID trace.TraceID, spanContext context.Context, finish func()) { + initCtx := context.Background() + rootCtx, rootSpan := tracer.Start(initCtx, "root") + childCtx, childSpan := tracer.Start(rootCtx, "child") + grandchildCtx, grandchildSpan := tracer.Start(childCtx, "grandChild") + + spanTraceID := grandchildSpan.SpanContext().TraceID() + return spanTraceID, grandchildCtx, func() { + grandchildSpan.End() + childSpan.End() + rootSpan.End() } }, expectedSpans: []string{ - "/grpc.testing.TestService/FullDuplexCall", + "grpc.testing.TestService/FullDuplexCall", "grandChild", "child", "root", @@ -207,42 +259,48 @@ func TestStreamPassthroughInterceptor(t *testing.T) { t.Run(tc.desc, func(t *testing.T) { reporter.Reset() - var parentID jaeger.SpanID + var traceID trace.TraceID service := &testSvc{ fullDuplexCall: func(stream grpc_testing.TestService_FullDuplexCallServer) error { _, err := stream.Recv() - require.NoError(t, err) - if span := opentracing.SpanFromContext(stream.Context()); span != nil { - parentID = span.(*jaeger.Span).SpanContext().ParentID() + require.Equal(t, err, io.EOF) + if span := trace.SpanFromContext(stream.Context()); span.SpanContext().IsValid() { + traceID = span.SpanContext().TraceID() } require.NoError(t, stream.Send(&grpc_testing.StreamingOutputCallResponse{})) return nil }, } - expectedParentID, spanContext, finishFunc := tc.setup(t) - client := startFakeGitalyServer(t, service, spanContext) - stream, err := client.FullDuplexCall(testhelper.Context(t)) - require.NoError(t, err) + expectedTraceID, contextToInject, finishFunc := tc.setup(t) - require.NoError(t, stream.Send(&grpc_testing.StreamingOutputCallRequest{})) + grpcClient := startFakeGitalyServer(t, contextToInject, service) + stream, err := grpcClient.FullDuplexCall(testhelper.Context(t)) + require.NoError(t, err) + require.NoError(t, stream.CloseSend()) resp, err := stream.Recv() require.NoError(t, err) testhelper.ProtoEqual(t, &grpc_testing.StreamingOutputCallResponse{}, resp) - resp, err = stream.Recv() - require.Equal(t, io.EOF, err) - require.Nil(t, resp) - finishFunc() - require.Equal(t, expectedParentID, parentID) - require.Equal(t, tc.expectedSpans, reportedSpans(t, reporter)) + // In the case where there is no root span to inject into + // the incoming rRPC calls, we cannot know in advance what the + // traceID of the span created by the gRPC middleware will be. + // So in that special case, when we cannot know in advance what + // traceID to expect, we return the empty one. That's why this check + // is needed. + if expectedTraceID.IsValid() { + require.Equal(t, expectedTraceID.String(), traceID.String()) + } + + require.Equal(t, tc.expectedSpans, reportedSpanNames(t, reporter)) }) } } +// testSvc is the gRPC server implementation for the fake server initialized below type testSvc struct { grpc_testing.UnimplementedTestServiceServer unaryCall func(context.Context, *grpc_testing.SimpleRequest) (*grpc_testing.SimpleResponse, error) @@ -257,16 +315,15 @@ func (ts *testSvc) FullDuplexCall(stream grpc_testing.TestService_FullDuplexCall return ts.fullDuplexCall(stream) } -func startFakeGitalyServer(t *testing.T, svc *testSvc, spanContext opentracing.SpanContext) grpc_testing.TestServiceClient { +// startFakeGitalyServer starts a test Gitaly server and returns a client already configured to +// communicate with the server. +func startFakeGitalyServer(t *testing.T, spanContext context.Context, svc *testSvc) grpc_testing.TestServiceClient { t.Helper() listener, err := net.Listen("tcp", "localhost:0") require.NoError(t, err) - srv := grpc.NewServer( - grpc.StreamInterceptor(grpctracing.StreamServerTracingInterceptor()), - grpc.UnaryInterceptor(grpctracing.UnaryServerTracingInterceptor()), - ) + srv := grpc.NewServer(grpc.StatsHandler(NewGRPCServerStatsHandler())) grpc_testing.RegisterTestServiceServer(srv, svc) go testhelper.MustServe(t, srv, listener) @@ -285,26 +342,12 @@ func startFakeGitalyServer(t *testing.T, svc *testSvc, spanContext opentracing.S return grpc_testing.NewTestServiceClient(conn) } -// envMap implements opentracing.TextMapReader and opentracing.TextMapWriter. It is used to create -// testing environment maps used in below tests -type envMap map[string]string - -func (e envMap) Set(key, val string) { - e[key] = val -} - -func (e envMap) ForeachKey(handler func(key string, val string) error) error { - for key, val := range e { - if err := handler(key, val); err != nil { - return err - } - } - return nil -} - -func (e envMap) toSlice() []string { +// envMapToSlice takes a map of key/value string pair and converts them into +// a slice where each key. and value are delimited with a `=` sign, the same +// way environment variables are defined in a .env file. +func envMapToSlice(envs map[string]string) []string { var envSlice []string - for key, value := range e { + for key, value := range envs { envSlice = append(envSlice, fmt.Sprintf("%s=%s", key, value)) } return envSlice diff --git a/internal/tracing/tracer.go b/internal/tracing/tracer.go index b4163d4f4e0..2efd6bd6fcb 100644 --- a/internal/tracing/tracer.go +++ b/internal/tracing/tracer.go @@ -121,7 +121,7 @@ func InitializeTracerProvider(ctx context.Context, serviceName string) (trace.Tr if resErr != nil { // wrap the error but do not return, as this error is not critical // for creating the tracer provider - warningError = fmt.Errorf("error creating resources: %w", err) + warningError = fmt.Errorf("error creating resources: %w", resErr) } // Create a new tracer provider with the exporter configured above diff --git a/internal/tracing/tracing.go b/internal/tracing/tracing.go index bae918fd070..27930c34631 100644 --- a/internal/tracing/tracing.go +++ b/internal/tracing/tracing.go @@ -3,52 +3,53 @@ package tracing import ( "context" - "github.com/opentracing/opentracing-go" - "gitlab.com/gitlab-org/labkit/tracing" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) -// Tags is a key-value map. It is used to set tags for a span -type Tags map[string]any +var tracerName = "gitaly" -// StartSpan creates a new span with name and options (mostly tags). This function is a wrapper for +var defaultNoopSpan = &noop.Span{} + +// StartSpan creates a new span with name, attributes and options. This function is a wrapper for // underlying tracing libraries. This method should only be used at the entrypoint of the program. -func StartSpan(ctx context.Context, spanName string, tags Tags, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) { - return opentracing.StartSpanFromContext(ctx, spanName, tagsToOpentracingTags(opts, tags)...) +// +//nolint:spancheck +func StartSpan(ctx context.Context, spanName string, attrs []attribute.KeyValue, opts ...trace.SpanStartOption) (trace.Span, context.Context) { + cctx, span := otel.GetTracerProvider().Tracer(tracerName).Start(ctx, spanName, opts...) + span.SetAttributes(attrs...) + + return span, cctx } // StartSpanIfHasParent creates a new span if the context already has an existing span. This function // adds a simple validation to prevent orphan spans outside interested code paths. It returns a dummy // span, which acts as normal span, but does absolutely nothing and is not recorded later. -func StartSpanIfHasParent(ctx context.Context, spanName string, tags Tags, opts ...opentracing.StartSpanOption) (opentracing.Span, context.Context) { - parent := opentracing.SpanFromContext(ctx) - if parent == nil { - return &NoopSpan{}, ctx +func StartSpanIfHasParent(ctx context.Context, spanName string, attrs []attribute.KeyValue, opts ...trace.SpanStartOption) (trace.Span, context.Context) { + if !trace.SpanFromContext(ctx).SpanContext().IsValid() { + return defaultNoopSpan, ctx } - return opentracing.StartSpanFromContext(ctx, spanName, tagsToOpentracingTags(opts, tags)...) + return StartSpan(ctx, spanName, attrs, opts...) } -// DiscardSpanInContext discards the current active span from the context. This function is helpful -// when the current code path enters an area shared by other code paths. Git catfile cache is a -// good example of this type of span. +// DiscardSpanInContext discards the current active span in the context by replacing it with a +// non-recording span. If the context does not contain any active span, it is left intact. +// This function is helpful when the current code path enters an area shared by other code +// paths. Git catfile cache is a good example of this type of span. func DiscardSpanInContext(ctx context.Context) context.Context { - if opentracing.SpanFromContext(ctx) == nil { + if !trace.SpanFromContext(ctx).SpanContext().IsValid() { return ctx } - return opentracing.ContextWithSpan(ctx, nil) + return trace.ContextWithSpan(ctx, nil) } // IsSampled tells whether a span belongs to a sampled trace func IsSampled(ctx context.Context) bool { - span := opentracing.SpanFromContext(ctx) + span := trace.SpanFromContext(ctx) if span != nil { - return tracing.IsSampled(span) + return span.SpanContext().IsSampled() } return false } - -func tagsToOpentracingTags(opts []opentracing.StartSpanOption, tags Tags) []opentracing.StartSpanOption { - for key, value := range tags { - opts = append(opts, opentracing.Tag{Key: key, Value: value}) - } - return opts -} diff --git a/internal/tracing/tracing_test.go b/internal/tracing/tracing_test.go index abd42871e2f..73a7f057734 100644 --- a/internal/tracing/tracing_test.go +++ b/internal/tracing/tracing_test.go @@ -3,146 +3,131 @@ package tracing import ( "testing" - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/log" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/uber/jaeger-client-go" "gitlab.com/gitlab-org/gitaly/v18/internal/testhelper" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" ) func TestCreateSpan(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() - - var span opentracing.Span - span, _ = StartSpan(testhelper.Context(t), "root", Tags{ - "tagRoot1": "value1", - "tagRoot2": "value2", - "tagRoot3": "value3", - }) - span.Finish() - - require.Equal(t, []string{"root"}, reportedSpans(t, reporter)) - require.Equal(t, Tags{ - "tagRoot1": "value1", - "tagRoot2": "value2", - "tagRoot3": "value3", - }, spanTags(span)) + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() + + ctx := testhelper.Context(t) + + spanAttributes := []attribute.KeyValue{ + attribute.String("tagRoot1", "value1"), + attribute.String("tagRoot2", "value2"), + attribute.String("tagRoot3", "value3"), + } + + span, _ := StartSpan(ctx, "root", spanAttributes) + span.End() + + generatedSpans := reporter.GetSpans() + require.Len(t, generatedSpans, 1) + + require.Equal(t, "root", generatedSpans[0].Name) + require.Equal(t, spanAttributes, generatedSpans[0].Attributes) } func TestCreateSpanIfHasParent_emptyContext(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() ctx := testhelper.Context(t) - var span, span2 opentracing.Span + var span, span2 trace.Span span, ctx = StartSpanIfHasParent(ctx, "should-not-report-root", nil) - span.SetBaggageItem("baggage", "baggageValue") - span.SetTag("tag", "tagValue") - span.LogFields(log.String("log", "logValue")) - span.LogKV("log2", "logValue") - span.Finish() + span.SetAttributes(attribute.String("tag", "tagValue")) + span.End() span2, _ = StartSpanIfHasParent(ctx, "should-not-report-child", nil) - span2.Finish() + span2.End() - require.Empty(t, reportedSpans(t, reporter)) + require.Empty(t, reporter.GetSpans()) } func TestCreateSpanIfHasParent_hasParent(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() ctx := testhelper.Context(t) - var span1, span2 opentracing.Span + var span1, span2 trace.Span span1, ctx = StartSpan(ctx, "root", nil) span2, _ = StartSpanIfHasParent(ctx, "child", nil) - span2.Finish() - span1.Finish() + span2.End() + span1.End() - spans := reportedSpans(t, reporter) + spans := reportedSpanNames(t, reporter) require.Equal(t, []string{"child", "root"}, spans) + require.Len(t, reporter.GetSpans(), 2) } func TestCreateSpanIfHasParent_hasParentWithTags(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() ctx := testhelper.Context(t) - var span1, span2 opentracing.Span - span1, ctx = StartSpan(ctx, "root", Tags{ - "tagRoot1": "value1", - "tagRoot2": "value2", - "tagRoot3": "value3", - }) - span2, _ = StartSpanIfHasParent(ctx, "child", Tags{ - "tagChild1": "value1", - "tagChild2": "value2", - "tagChild3": "value3", - }) - span2.Finish() - span1.Finish() - - spans := reportedSpans(t, reporter) - require.Equal(t, []string{"child", "root"}, spans) - require.Equal(t, Tags{ - "tagRoot1": "value1", - "tagRoot2": "value2", - "tagRoot3": "value3", - }, spanTags(span1)) - require.Equal(t, Tags{ - "tagChild1": "value1", - "tagChild2": "value2", - "tagChild3": "value3", - }, spanTags(span2)) + var span1, span2 trace.Span + span1Attributes := []attribute.KeyValue{ + attribute.String("tagRoot1", "value1"), + attribute.String("tagRoot2", "value2"), + attribute.String("tagRoot3", "value3"), + } + span1, ctx = StartSpan(ctx, "root", span1Attributes) + + span2Attributes := []attribute.KeyValue{ + attribute.String("tagChild1", "value1"), + attribute.String("tagChild2", "value2"), + attribute.String("tagChild3", "value3"), + } + span2, _ = StartSpanIfHasParent(ctx, "child", span2Attributes) + + span2.End() + span1.End() + + require.Equal(t, []string{"child", "root"}, reportedSpanNames(t, reporter)) + + recordedSpans := reporter.GetSpans() + require.Len(t, recordedSpans, 2) + + require.Equal(t, span1Attributes, recordedSpans[1].Attributes) + require.Equal(t, span2Attributes, recordedSpans[0].Attributes) } func TestDiscardSpanInContext_emptyContext(t *testing.T) { ctx := DiscardSpanInContext(testhelper.Context(t)) - require.Nil(t, opentracing.SpanFromContext(ctx)) + span := trace.SpanFromContext(ctx) + require.False(t, span.IsRecording()) } func TestDiscardSpanInContext_hasParent(t *testing.T) { - reporter, cleanup := testhelper.StubTracingReporter(t) - defer cleanup() + reporter := testhelper.NewStubTracingReporter(t) + defer func() { _ = reporter.Close() }() ctx := testhelper.Context(t) - var span1, span2, span3 opentracing.Span + var span1, span2, span3 trace.Span span1, ctx = StartSpan(ctx, "root", nil) span2, ctx = StartSpanIfHasParent(ctx, "child", nil) ctx = DiscardSpanInContext(ctx) span3, _ = StartSpanIfHasParent(ctx, "discarded", nil) - span3.Finish() - span2.Finish() - span1.Finish() + span3.End() + span2.End() + span1.End() - spans := reportedSpans(t, reporter) + spans := reportedSpanNames(t, reporter) require.Equal(t, []string{"child", "root"}, spans) } -func reportedSpans(t *testing.T, reporter *jaeger.InMemoryReporter) []string { +func reportedSpanNames(_ *testing.T, reporter *testhelper.StubTracingReporter) []string { var names []string for _, span := range reporter.GetSpans() { - if !assert.IsType(t, span, &jaeger.Span{}) { - continue - } - jaegerSpan := span.(*jaeger.Span) - names = append(names, jaegerSpan.OperationName()) + names = append(names, span.Name) } return names } - -func spanTags(span opentracing.Span) Tags { - tags := Tags{} - jaegerSpan := span.(*jaeger.Span) - for key, value := range jaegerSpan.Tags() { - tags[key] = value - } - return tags -} -- GitLab