diff --git a/cmd/adapter/main.go b/cmd/adapter/main.go index beb542c..8933ac4 100644 --- a/cmd/adapter/main.go +++ b/cmd/adapter/main.go @@ -444,7 +444,7 @@ func runServe(flags *pflag.FlagSet) error { // Initialize OpenTelemetry sampleRatio := otel.GetTraceSampleRatio(log, ctx) - tp, err := otel.InitTracer(config.Adapter.Name, version.Version, sampleRatio) + tp, err := otel.InitTracer(log, config.Adapter.Name, version.Version, sampleRatio) if err != nil { errCtx := logger.WithErrorField(ctx, err) log.Errorf(errCtx, "Failed to initialize OpenTelemetry") diff --git a/go.mod b/go.mod index 3fd91fd..8838cf2 100644 --- a/go.mod +++ b/go.mod @@ -7,20 +7,24 @@ require ( github.com/cloudevents/sdk-go/v2 v2.16.2 github.com/docker/go-connections v0.6.0 github.com/go-playground/validator/v10 v10.30.1 + github.com/go-viper/mapstructure/v2 v2.5.0 github.com/google/cel-go v0.26.1 github.com/mitchellh/copystructure v1.2.0 github.com/openshift-hyperfleet/hyperfleet-broker v1.1.0 github.com/openshift-online/maestro v0.0.0-20260202062555-48b47506a254 github.com/openshift-online/ocm-sdk-go v0.1.493 github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/client_model v0.6.2 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/testcontainers/testcontainers-go v0.40.0 - go.opentelemetry.io/otel v1.40.0 - go.opentelemetry.io/otel/sdk v1.40.0 - go.opentelemetry.io/otel/trace v1.40.0 + go.opentelemetry.io/otel v1.42.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 + go.opentelemetry.io/otel/sdk v1.42.0 + go.opentelemetry.io/otel/trace v1.42.0 golang.org/x/text v0.34.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.34.3 @@ -32,7 +36,7 @@ require ( ) require ( - cel.dev/expr v0.24.0 // indirect + cel.dev/expr v0.25.1 // indirect cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/auth v0.18.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect @@ -78,7 +82,6 @@ require ( github.com/go-openapi/swag v0.23.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.2.5 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -88,6 +91,7 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect github.com/googleapis/gax-go/v2 v2.17.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -118,7 +122,6 @@ require ( github.com/pkg/errors v0.9.1 // 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/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.4 // indirect github.com/prometheus/procfs v0.19.2 // indirect github.com/rabbitmq/amqp091-go v1.10.0 // indirect @@ -139,14 +142,16 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect + go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/net v0.50.0 // indirect + golang.org/x/net v0.51.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect @@ -156,7 +161,7 @@ require ( google.golang.org/genproto v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect - google.golang.org/grpc v1.78.0 // indirect + google.golang.org/grpc v1.79.2 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 5ae844c..051c687 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= -cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= @@ -49,8 +49,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cloudevents/sdk-go/v2 v2.16.2 h1:ZYDFrYke4FD+jM8TZTJJO6JhKHzOQl2oqpFK1D+NnQM= github.com/cloudevents/sdk-go/v2 v2.16.2/go.mod h1:laOcGImm4nVJEU+PHnUrKL56CKmRL65RlQF0kRmW/kg= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= -github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= +github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -83,12 +83,12 @@ github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRr github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= -github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs= +github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= +github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= +github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= +github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= +github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= @@ -181,8 +181,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1Znvmczt github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8 h1:NpbJl/eVbvrGE0MJ6X16X9SAifesl6Fwxg/YmCvubRI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.8/go.mod h1:mi7YA+gCzVem12exXy46ZespvGtX/lZmD/RLnQhVW7U= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -348,20 +348,22 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 h1:Ckwye2FpXkYgiHX7fyVrN1uA/UYd9ounqqTuSNAv0k4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0/go.mod h1:teIFJh5pW2y+AN7riv6IBPX2DuesS3HgP39mwOspKwU= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -398,8 +400,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= @@ -462,8 +464,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= -google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU= +google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/pkg/otel/tracer.go b/pkg/otel/tracer.go index 218c4c9..171915b 100644 --- a/pkg/otel/tracer.go +++ b/pkg/otel/tracer.go @@ -6,9 +6,12 @@ import ( "fmt" "os" "strconv" + "strings" "github.com/openshift-hyperfleet/hyperfleet-adapter/pkg/logger" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" @@ -23,6 +26,23 @@ const ( // DefaultTraceSampleRatio is the default trace sampling ratio (10% of traces) // Can be overridden via TRACE_SAMPLE_RATIO env var DefaultTraceSampleRatio = 0.1 + + // envOtelExporterOtlpEndpoint is the standard OTel env var for the OTLP endpoint + envOtelExporterOtlpEndpoint = "OTEL_EXPORTER_OTLP_ENDPOINT" + + // envOtelExporterOtlpTracesEndpoint is the signal-specific OTel env var for the traces endpoint + envOtelExporterOtlpTracesEndpoint = "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT" + + // envOtelExporterOtlpProtocol is the standard OTel env var for the OTLP protocol + envOtelExporterOtlpProtocol = "OTEL_EXPORTER_OTLP_PROTOCOL" + + // envOtelExporterOtlpTracesProtocol is the signal-specific OTel env var for the traces protocol + envOtelExporterOtlpTracesProtocol = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" + + // defaultOtlpProtocol is the default OTLP protocol when none is specified. + // Per OTel spec, the default SHOULD be "http/protobuf". + // See: https://opentelemetry.io/docs/specs/otel/protocol/exporter/ + defaultOtlpProtocol = "http/protobuf" ) // GetTraceSampleRatio reads the trace sample ratio from TRACE_SAMPLE_RATIO env var. @@ -65,21 +85,79 @@ func GetTraceSampleRatio(log logger.Logger, ctx context.Context) float64 { return ratio } -// InitTracer initializes OpenTelemetry TracerProvider for generating trace_id and span_id. -// These IDs are used for: -// 1. Log correlation (via logger.WithOTelTraceContext) -// 2. HTTP request propagation (via W3C Trace Context headers) +// createExporter creates an OTLP SpanExporter when OTEL_EXPORTER_OTLP_ENDPOINT is set. +// Returns nil when no endpoint is configured (spans remain local-only for trace ID generation). +// The protocol defaults to http/protobuf (per OTel spec), configurable via OTEL_EXPORTER_OTLP_PROTOCOL. +func createExporter(ctx context.Context, log logger.Logger) (sdktrace.SpanExporter, error) { + // Check if an OTLP endpoint is configured (presence check only). + // The actual endpoint value is read by the OTel SDK from env vars directly, + // so we don't pass otlpEndpoint to the exporter constructors. + otlpEndpoint := os.Getenv(envOtelExporterOtlpTracesEndpoint) + if otlpEndpoint == "" { + otlpEndpoint = os.Getenv(envOtelExporterOtlpEndpoint) + } + if otlpEndpoint == "" { + log.Infof(ctx, "No %s or %s configured, traces will not be exported"+ + " (trace IDs still generated for log correlation)", + envOtelExporterOtlpTracesEndpoint, envOtelExporterOtlpEndpoint) + return nil, nil + } + + protocol := os.Getenv(envOtelExporterOtlpTracesProtocol) + protocolSource := envOtelExporterOtlpTracesProtocol + if protocol == "" { + protocol = os.Getenv(envOtelExporterOtlpProtocol) + protocolSource = envOtelExporterOtlpProtocol + } + var exporter sdktrace.SpanExporter + var err error + + switch strings.ToLower(protocol) { + case "grpc": + exporter, err = otlptracegrpc.New(ctx) + case defaultOtlpProtocol, "http", "": // http/protobuf (default per OTel spec), http, or unset + protocol = defaultOtlpProtocol + exporter, err = otlptracehttp.New(ctx) + default: + log.Warnf(ctx, "Unrecognized %s value %q, using default %s", + protocolSource, protocol, defaultOtlpProtocol) + protocol = defaultOtlpProtocol + exporter, err = otlptracehttp.New(ctx) + } + if err != nil { + return nil, fmt.Errorf("failed to create OTLP exporter (protocol=%s): %w", protocol, err) + } + + log.Infof(ctx, "OTLP trace exporter configured: protocol=%s", protocol) + return exporter, nil +} + +// InitTracer initializes OpenTelemetry TracerProvider with optional span export. +// +// When OTEL_EXPORTER_OTLP_ENDPOINT is set, spans are batched and exported via OTLP +// (http/protobuf by default, or gRPC via OTEL_EXPORTER_OTLP_PROTOCOL). +// When no endpoint is configured, the TracerProvider still generates trace IDs and span IDs +// for log correlation and W3C context propagation, but spans are not exported. // // The sampler uses ParentBased(TraceIDRatioBased(sampleRatio)) which: -// - Respects the parent span's sampling decision when present (from traceparent header) -// - Applies probabilistic sampling for root spans based on sampleRatio -// This allows distributed tracing visibility while controlling observability costs. -func InitTracer(serviceName, serviceVersion string, sampleRatio float64) (*sdktrace.TracerProvider, error) { +// - Respects the parent span's sampling decision when present (from traceparent header) +// - Applies probabilistic sampling for root spans based on sampleRatio +func InitTracer( + log logger.Logger, serviceName, serviceVersion string, sampleRatio float64, +) (*sdktrace.TracerProvider, error) { + ctx := context.Background() + + // Create exporter (nil when no OTLP endpoint configured) + exporter, err := createExporter(ctx, log) + if err != nil { + return nil, fmt.Errorf("failed to create trace exporter: %w", err) + } + // Create resource with service attributes. // Note: We don't merge with resource.Default() to avoid schema URL conflicts // between the SDK's bundled semconv version and our imported version. res, err := resource.New( - context.Background(), + ctx, resource.WithAttributes( semconv.ServiceName(serviceName), semconv.ServiceVersion(serviceVersion), @@ -89,6 +167,11 @@ func InitTracer(serviceName, serviceVersion string, sampleRatio float64) (*sdktr resource.WithHost(), ) if err != nil { + if exporter != nil { + if shutdownErr := exporter.Shutdown(ctx); shutdownErr != nil { + log.Warnf(ctx, "Failed to shutdown exporter during cleanup: %v", shutdownErr) + } + } return nil, fmt.Errorf("failed to create resource: %w", err) } @@ -98,10 +181,15 @@ func InitTracer(serviceName, serviceVersion string, sampleRatio float64) (*sdktr // This enables proper sampling propagation across service boundaries sampler := sdktrace.ParentBased(sdktrace.TraceIDRatioBased(sampleRatio)) - tp := sdktrace.NewTracerProvider( + opts := []sdktrace.TracerProviderOption{ sdktrace.WithResource(res), sdktrace.WithSampler(sampler), - ) + } + if exporter != nil { + opts = append(opts, sdktrace.WithBatcher(exporter)) + } + + tp := sdktrace.NewTracerProvider(opts...) otel.SetTracerProvider(tp) // TraceContext propagator handles W3C traceparent/tracestate headers // ensuring sampling decisions propagate through message headers diff --git a/pkg/otel/tracer_test.go b/pkg/otel/tracer_test.go new file mode 100644 index 0000000..0ee0fc1 --- /dev/null +++ b/pkg/otel/tracer_test.go @@ -0,0 +1,167 @@ +package otel + +import ( + "context" + "testing" + + "github.com/openshift-hyperfleet/hyperfleet-adapter/pkg/logger" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func testLogger() logger.Logger { + log, _ := logger.NewLogger(logger.Config{Level: "error", Output: "stdout", Format: "json"}) + return log +} + +func TestGetTraceSampleRatio(t *testing.T) { + log := testLogger() + ctx := context.Background() + + t.Run("default when not set", func(t *testing.T) { + t.Setenv(EnvTraceSampleRatio, "") + ratio := GetTraceSampleRatio(log, ctx) + assert.Equal(t, DefaultTraceSampleRatio, ratio) + }) + + t.Run("valid ratio", func(t *testing.T) { + t.Setenv(EnvTraceSampleRatio, "0.5") + ratio := GetTraceSampleRatio(log, ctx) + assert.Equal(t, 0.5, ratio) + }) + + t.Run("invalid string", func(t *testing.T) { + t.Setenv(EnvTraceSampleRatio, "notanumber") + ratio := GetTraceSampleRatio(log, ctx) + assert.Equal(t, DefaultTraceSampleRatio, ratio) + }) + + t.Run("out of range positive", func(t *testing.T) { + t.Setenv(EnvTraceSampleRatio, "2.0") + ratio := GetTraceSampleRatio(log, ctx) + assert.Equal(t, DefaultTraceSampleRatio, ratio) + }) + + t.Run("out of range negative", func(t *testing.T) { + t.Setenv(EnvTraceSampleRatio, "-0.5") + ratio := GetTraceSampleRatio(log, ctx) + assert.Equal(t, DefaultTraceSampleRatio, ratio) + }) + + t.Run("zero is valid", func(t *testing.T) { + t.Setenv(EnvTraceSampleRatio, "0.0") + ratio := GetTraceSampleRatio(log, ctx) + assert.Equal(t, 0.0, ratio) + }) + + t.Run("one is valid", func(t *testing.T) { + t.Setenv(EnvTraceSampleRatio, "1.0") + ratio := GetTraceSampleRatio(log, ctx) + assert.Equal(t, 1.0, ratio) + }) +} + +func TestCreateExporter(t *testing.T) { + log := testLogger() + ctx := context.Background() + + // clearOtelEnv ensures all 4 OTel env vars are cleared to prevent + // interference from the local shell environment. + clearOtelEnv := func(t *testing.T) { + t.Setenv(envOtelExporterOtlpEndpoint, "") + t.Setenv(envOtelExporterOtlpTracesEndpoint, "") + t.Setenv(envOtelExporterOtlpProtocol, "") + t.Setenv(envOtelExporterOtlpTracesProtocol, "") + } + + t.Run("nil exporter when no endpoint set", func(t *testing.T) { + clearOtelEnv(t) + exporter, err := createExporter(ctx, log) + require.NoError(t, err) + assert.Nil(t, exporter) + }) + + t.Run("http exporter when endpoint set with default protocol", func(t *testing.T) { + clearOtelEnv(t) + t.Setenv(envOtelExporterOtlpEndpoint, "http://localhost:4318") + exporter, err := createExporter(ctx, log) + require.NoError(t, err) + assert.NotNil(t, exporter) + assert.NoError(t, exporter.Shutdown(ctx)) + }) + + t.Run("grpc exporter when protocol is grpc", func(t *testing.T) { + clearOtelEnv(t) + t.Setenv(envOtelExporterOtlpEndpoint, "localhost:4317") + t.Setenv(envOtelExporterOtlpProtocol, "grpc") + exporter, err := createExporter(ctx, log) + require.NoError(t, err) + assert.NotNil(t, exporter) + assert.NoError(t, exporter.Shutdown(ctx)) + }) + + t.Run("falls back to http/protobuf for unrecognized protocol", func(t *testing.T) { + clearOtelEnv(t) + t.Setenv(envOtelExporterOtlpEndpoint, "http://localhost:4318") + t.Setenv(envOtelExporterOtlpProtocol, "unknown-protocol") + exporter, err := createExporter(ctx, log) + require.NoError(t, err) + assert.NotNil(t, exporter) + assert.NoError(t, exporter.Shutdown(ctx)) + }) + + t.Run("traces-specific endpoint takes precedence", func(t *testing.T) { + clearOtelEnv(t) + t.Setenv(envOtelExporterOtlpTracesEndpoint, "http://localhost:4318") + exporter, err := createExporter(ctx, log) + require.NoError(t, err) + assert.NotNil(t, exporter) + assert.NoError(t, exporter.Shutdown(ctx)) + }) + + t.Run("traces-specific protocol takes precedence", func(t *testing.T) { + clearOtelEnv(t) + t.Setenv(envOtelExporterOtlpEndpoint, "http://localhost:4318") + t.Setenv(envOtelExporterOtlpProtocol, "grpc") + t.Setenv(envOtelExporterOtlpTracesProtocol, "http/protobuf") + exporter, err := createExporter(ctx, log) + require.NoError(t, err) + assert.NotNil(t, exporter) + assert.NoError(t, exporter.Shutdown(ctx)) + }) + + t.Run("nil when neither endpoint is set", func(t *testing.T) { + clearOtelEnv(t) + exporter, err := createExporter(ctx, log) + require.NoError(t, err) + assert.Nil(t, exporter) + }) +} + +func TestInitTracer(t *testing.T) { + log := testLogger() + + clearOtelEnv := func(t *testing.T) { + t.Setenv(envOtelExporterOtlpEndpoint, "") + t.Setenv(envOtelExporterOtlpTracesEndpoint, "") + t.Setenv(envOtelExporterOtlpProtocol, "") + t.Setenv(envOtelExporterOtlpTracesProtocol, "") + } + + t.Run("initializes without exporter when no endpoint", func(t *testing.T) { + clearOtelEnv(t) + tp, err := InitTracer(log, "test-service", "0.0.1", 1.0) + require.NoError(t, err) + require.NotNil(t, tp) + assert.NoError(t, tp.Shutdown(context.Background())) + }) + + t.Run("initializes with exporter when endpoint is set", func(t *testing.T) { + clearOtelEnv(t) + t.Setenv(envOtelExporterOtlpEndpoint, "http://localhost:4318") + tp, err := InitTracer(log, "test-service", "0.0.1", 1.0) + require.NoError(t, err) + require.NotNil(t, tp) + assert.NoError(t, tp.Shutdown(context.Background())) + }) +}