From 25e8164a16a8393a9b97044d2d54e47cc9f63b6e Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Mon, 12 Jan 2026 16:41:43 -0800 Subject: [PATCH 01/14] Add OpenTelemetry tracing support Integrate slog-otel to automatically include trace_id and span_id from OpenTelemetry context in log output, enabling correlation between logs and distributed traces. Changes: - pkg/log/log.go: Wrap JSON handler with slogotel.OtelHandler - pkg/utils/otel_test.go: Fix TestNewPropagator to pass OTelConfig - charts/: Bump version to 0.3.5 - go.mod: Add github.com/remychantenay/slog-otel v1.3.4 Issue: LFXV2-608 Signed-off-by: Trevor Bramwell Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- charts/lfx-v2-auth-service/Chart.yaml | 2 +- .../templates/deployment.yaml | 43 ++ charts/lfx-v2-auth-service/values.yaml | 38 ++ cmd/server/http.go | 3 + cmd/server/main.go | 16 + go.mod | 31 +- go.sum | 71 +++- pkg/log/log.go | 9 +- pkg/logging/context.go | 37 ++ pkg/utils/otel.go | 393 +++++++++++++++++ pkg/utils/otel_test.go | 399 ++++++++++++++++++ 11 files changed, 1020 insertions(+), 22 deletions(-) create mode 100644 pkg/logging/context.go create mode 100644 pkg/utils/otel.go create mode 100644 pkg/utils/otel_test.go diff --git a/charts/lfx-v2-auth-service/Chart.yaml b/charts/lfx-v2-auth-service/Chart.yaml index 5e98281..313d91f 100644 --- a/charts/lfx-v2-auth-service/Chart.yaml +++ b/charts/lfx-v2-auth-service/Chart.yaml @@ -5,5 +5,5 @@ apiVersion: v2 name: lfx-v2-auth-service description: LFX Platform V2 Auth Service chart type: application -version: 0.3.4 +version: 0.3.5 appVersion: "latest" diff --git a/charts/lfx-v2-auth-service/templates/deployment.yaml b/charts/lfx-v2-auth-service/templates/deployment.yaml index 325361b..12126ea 100644 --- a/charts/lfx-v2-auth-service/templates/deployment.yaml +++ b/charts/lfx-v2-auth-service/templates/deployment.yaml @@ -35,6 +35,49 @@ spec: {{- toYaml $config.valueFrom | nindent 14 }} {{- end }} {{- end }} + {{- with .Values.app.extraEnv }} + {{- toYaml . | nindent 10 }} + {{- end }} + {{- if .Values.app.otel.serviceName }} + - name: OTEL_SERVICE_NAME + value: {{ .Values.app.otel.serviceName | quote }} + {{- end }} + {{- if .Values.app.otel.serviceVersion }} + - name: OTEL_SERVICE_VERSION + value: {{ .Values.app.otel.serviceVersion | quote }} + {{- end }} + {{- if .Values.app.otel.endpoint }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: {{ .Values.app.otel.endpoint | quote }} + {{- end }} + {{- if .Values.app.otel.protocol }} + - name: OTEL_EXPORTER_OTLP_PROTOCOL + value: {{ .Values.app.otel.protocol | quote }} + {{- end }} + {{- if .Values.app.otel.insecure }} + - name: OTEL_EXPORTER_OTLP_INSECURE + value: {{ .Values.app.otel.insecure | quote }} + {{- end }} + {{- if .Values.app.otel.tracesExporter }} + - name: OTEL_TRACES_EXPORTER + value: {{ .Values.app.otel.tracesExporter | quote }} + {{- end }} + {{- if .Values.app.otel.tracesSampleRatio }} + - name: OTEL_TRACES_SAMPLE_RATIO + value: {{ .Values.app.otel.tracesSampleRatio | quote }} + {{- end }} + {{- if .Values.app.otel.metricsExporter }} + - name: OTEL_METRICS_EXPORTER + value: {{ .Values.app.otel.metricsExporter | quote }} + {{- end }} + {{- if .Values.app.otel.logsExporter }} + - name: OTEL_LOGS_EXPORTER + value: {{ .Values.app.otel.logsExporter | quote }} + {{- end }} + {{- if .Values.app.otel.propagators }} + - name: OTEL_PROPAGATORS + value: {{ .Values.app.otel.propagators | quote }} + {{- end }} ports: - containerPort: {{ .Values.service.port }} name: web diff --git a/charts/lfx-v2-auth-service/values.yaml b/charts/lfx-v2-auth-service/values.yaml index 9f66b74..e730a75 100644 --- a/charts/lfx-v2-auth-service/values.yaml +++ b/charts/lfx-v2-auth-service/values.yaml @@ -138,3 +138,41 @@ app: value: authelia-users AUTHELIA_OIDC_USERINFO_URL: value: https://auth.k8s.orb.local/api/oidc/userinfo + # extraEnv allows injecting additional environment variables before + # other configurations + extraEnv: [] + # otel is the configuration for OpenTelemetry tracing + otel: + # serviceName is the service name for OpenTelemetry resource identification + # (default: "lfx-v2-auth-service") + serviceName: "" + # serviceVersion is the service version for OpenTelemetry resource + # identification + # (default: "1.0.0") + serviceVersion: "" + # protocol specifies the OTLP protocol: "grpc" or "http" + # (default: "grpc") + protocol: "grpc" + # endpoint is the OTLP collector endpoint + # For gRPC: typically "host:4317", for HTTP: typically "host:4318" + endpoint: "" + # insecure disables TLS for the OTLP connection + # Set to "true" for in-cluster communication without TLS + insecure: "false" + # tracesExporter specifies the traces exporter: "otlp" or "none" + # (default: "none") + tracesExporter: "none" + # tracesSampleRatio specifies the sampling ratio for traces (0.0 to 1.0) + # A value of 1.0 means all traces are sampled, 0.5 means 50% are sampled + # (default: "1.0") + tracesSampleRatio: "1.0" + # metricsExporter specifies the metrics exporter: "otlp" or "none" + # (default: "none") + metricsExporter: "none" + # logsExporter specifies the logs exporter: "otlp" or "none" + # (default: "none") + logsExporter: "none" + # propagators specifies the propagators to use, comma-separated + # Supported values: "tracecontext", "baggage", "jaeger" + # (default: "tracecontext,baggage") + propagators: "tracecontext,baggage,jaeger" diff --git a/cmd/server/http.go b/cmd/server/http.go index 28b6ae0..dade313 100644 --- a/cmd/server/http.go +++ b/cmd/server/http.go @@ -13,6 +13,7 @@ import ( authservice "github.com/linuxfoundation/lfx-v2-auth-service/gen/auth_service" authserver "github.com/linuxfoundation/lfx-v2-auth-service/gen/http/auth_service/server" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "goa.design/clue/debug" goahttp "goa.design/goa/v3/http" ) @@ -60,6 +61,8 @@ func handleHTTPServer(ctx context.Context, host string, authEndpoints *authservi // Log query and response bodies if debug logs are enabled. handler = debug.HTTP()(handler) } + // Wrap the handler with OpenTelemetry instrumentation + handler = otelhttp.NewHandler(handler, "auth-service") // Start HTTP server using default configuration, change the code to // configure the server as required by your service. diff --git a/cmd/server/main.go b/cmd/server/main.go index 9ea818e..4fe9985 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -18,6 +18,7 @@ import ( authservice "github.com/linuxfoundation/lfx-v2-auth-service/gen/auth_service" logging "github.com/linuxfoundation/lfx-v2-auth-service/pkg/log" + "github.com/linuxfoundation/lfx-v2-auth-service/pkg/utils" ) const ( @@ -47,6 +48,21 @@ func main() { flag.Parse() ctx := context.Background() + + // Set up OpenTelemetry SDK. + otelConfig := utils.OTelConfigFromEnv() + otelShutdown, err := utils.SetupOTelSDKWithConfig(ctx, otelConfig) + if err != nil { + slog.ErrorContext(ctx, "error setting up OpenTelemetry SDK", "error", err) + os.Exit(1) + } + // Handle shutdown properly so nothing leaks. + defer func() { + if shutdownErr := otelShutdown(context.Background()); shutdownErr != nil { + slog.ErrorContext(ctx, "error shutting down OpenTelemetry SDK", "error", shutdownErr) + } + }() + slog.InfoContext(ctx, "Starting auth service", "bind", *bind, "http-port", *port, diff --git a/go.mod b/go.mod index c95212d..941a87d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,22 @@ require ( github.com/google/uuid v1.6.0 github.com/lestrrat-go/jwx/v2 v2.1.6 github.com/nats-io/nats.go v1.45.0 + github.com/remychantenay/slog-otel v1.3.4 github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 + go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 + go.opentelemetry.io/otel v1.39.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 + go.opentelemetry.io/otel/log v0.15.0 + go.opentelemetry.io/otel/sdk v1.39.0 + go.opentelemetry.io/otel/sdk/log v0.15.0 + go.opentelemetry.io/otel/sdk/metric v1.39.0 + go.opentelemetry.io/otel/trace v1.39.0 go.yaml.in/yaml/v2 v2.4.2 goa.design/clue v1.2.3 goa.design/goa/v3 v3.23.3 @@ -26,13 +41,17 @@ require ( require ( github.com/PuerkitoBio/rehttp v1.4.0 // indirect github.com/aws/smithy-go v1.23.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-chi/chi/v5 v5.2.3 // indirect github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect @@ -41,6 +60,7 @@ require ( github.com/gohugoio/hashstructure v0.6.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect @@ -62,17 +82,20 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/x448/float16 v0.8.4 // indirect go.devnw.com/structs v1.0.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.30.0 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.38.0 // indirect + golang.org/x/sys v0.39.0 // indirect golang.org/x/term v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.39.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/go.sum b/go.sum index 97aa58d..7ddeb46 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/aybabtme/iocontrol v0.0.0-20150809002002-ad15bcfc95a0 h1:0NmehRCgyk5r github.com/aybabtme/iocontrol v0.0.0-20150809002002-ad15bcfc95a0/go.mod h1:6L7zgvqo0idzI7IO8de6ZC051AfXb5ipkIJ7bIA2tGA= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -21,10 +23,13 @@ github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbr github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -60,6 +65,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -117,8 +124,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -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/remychantenay/slog-otel v1.3.4 h1:xoM41ayLff2U8zlK5PH31XwD7Lk3W9wKfl4+RcmKom4= +github.com/remychantenay/slog-otel v1.3.4/go.mod h1:ZkazuFMICKGDrO0r1njxKRdjTt/YcXKn6v2+0q/b0+U= +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/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -144,18 +153,46 @@ go.devnw.com/structs v1.0.0 h1:FFkBoBOkapCdxFEIkpOZRmMOMr9b9hxjKTD3bJYl9lk= go.devnw.com/structs v1.0.0/go.mod h1:wHBkdQpNeazdQHszJ2sxwVEpd8zGTEsKkeywDLGbrmg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +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.39.0 h1:Gz3yKzfMSEFzF0Vy5eIpu9ndpo4DhXMCxsLMF0OOApo= +go.opentelemetry.io/contrib/propagators/jaeger v1.39.0/go.mod h1:2D/cxxCqTlrday0rZrPujjg5aoAdqk1NaNyoXn8FJn8= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v3pa5PgVf2xoFMi5YtNR06WtS7ve5pcvLtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0/go.mod h1:JM31r0GGZ/GU94mX8hN4D8v6e40aFlUECSQ48HaLgHM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 h1:EKpiGphOYq3CYnIe2eX9ftUkyU+Y8Dtte8OaWyHJ4+I= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0/go.mod h1:nWFP7C+T8TygkTjJ7mAyEaFaE7wNfms3nV/vexZ6qt0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= -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/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= +go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= +go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.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= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -192,8 +229,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= @@ -216,8 +253,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= diff --git a/pkg/log/log.go b/pkg/log/log.go index 6014d1d..d15db4c 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -8,6 +8,8 @@ import ( "log" "log/slog" "os" + + slogotel "github.com/remychantenay/slog-otel" ) type ctxKey string @@ -101,7 +103,12 @@ func InitStructureLogConfig() { } h = slog.NewJSONHandler(os.Stdout, logOptions) log.SetFlags(log.Llongfile) - logger := contextHandler{h} + + // Wrap with slog-otel handler to add trace_id and span_id from context + otelHandler := slogotel.OtelHandler{Next: h} + + // Wrap with contextHandler to support context-based attributes + logger := contextHandler{otelHandler} slog.SetDefault(slog.New(logger)) } diff --git a/pkg/logging/context.go b/pkg/logging/context.go new file mode 100644 index 0000000..c1c604b --- /dev/null +++ b/pkg/logging/context.go @@ -0,0 +1,37 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package logging + +import ( + "context" + "log/slog" + + "go.opentelemetry.io/otel/trace" +) + +// LogAttrsFromContext extracts trace_id and span_id from the context and returns them as slog attributes. +// Use this to add tracing context to log messages for correlation. +func LogAttrsFromContext(ctx context.Context) []slog.Attr { + spanCtx := trace.SpanContextFromContext(ctx) + if !spanCtx.IsValid() { + return nil + } + return []slog.Attr{ + slog.String("trace_id", spanCtx.TraceID().String()), + slog.String("span_id", spanCtx.SpanID().String()), + } +} + +// LogWithContext returns a logger with trace context attributes added. +// Usage: logging.LogWithContext(ctx, slog.Default()).Info("message", "key", "value") +func LogWithContext(ctx context.Context, logger *slog.Logger) *slog.Logger { + spanCtx := trace.SpanContextFromContext(ctx) + if !spanCtx.IsValid() { + return logger + } + return logger.With( + slog.String("trace_id", spanCtx.TraceID().String()), + slog.String("span_id", spanCtx.SpanID().String()), + ) +} diff --git a/pkg/utils/otel.go b/pkg/utils/otel.go new file mode 100644 index 0000000..69155e9 --- /dev/null +++ b/pkg/utils/otel.go @@ -0,0 +1,393 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package utils + +import ( + "context" + "errors" + "log/slog" + "os" + "strconv" + "strings" + "time" + + "go.opentelemetry.io/contrib/propagators/jaeger" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/log/global" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/log" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/resource" + "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" +) + +const ( + // OTelProtocolGRPC configures OTLP exporters to use gRPC protocol. + OTelProtocolGRPC = "grpc" + // OTelProtocolHTTP configures OTLP exporters to use HTTP protocol. + OTelProtocolHTTP = "http" + + // OTelExporterOTLP configures signals to export via OTLP. + OTelExporterOTLP = "otlp" + // OTelExporterNone disables exporting for a signal. + OTelExporterNone = "none" +) + +// OTelConfig holds OpenTelemetry configuration options. +type OTelConfig struct { + // ServiceName is the name of the service for resource identification. + // Env: OTEL_SERVICE_NAME (default: "lfx-v2-auth-service") + ServiceName string + // ServiceVersion is the version of the service. + // Env: OTEL_SERVICE_VERSION + ServiceVersion string + // Protocol specifies the OTLP protocol to use: "grpc" or "http". + // Env: OTEL_EXPORTER_OTLP_PROTOCOL (default: "grpc") + Protocol string + // Endpoint is the OTLP collector endpoint. + // For gRPC: typically "localhost:4317" + // For HTTP: typically "localhost:4318" + // Env: OTEL_EXPORTER_OTLP_ENDPOINT + Endpoint string + // Insecure disables TLS for the connection. + // Env: OTEL_EXPORTER_OTLP_INSECURE (set to "true" for insecure connections) + Insecure bool + // TracesExporter specifies the traces exporter: "otlp" or "none". + // Env: OTEL_TRACES_EXPORTER (default: "none") + TracesExporter string + // TracesSampleRatio specifies the sampling ratio for traces (0.0 to 1.0). + // A value of 1.0 means all traces are sampled, 0.5 means 50% are sampled. + // Env: OTEL_TRACES_SAMPLE_RATIO (default: 1.0) + TracesSampleRatio float64 + // MetricsExporter specifies the metrics exporter: "otlp" or "none". + // Env: OTEL_METRICS_EXPORTER (default: "none") + MetricsExporter string + // LogsExporter specifies the logs exporter: "otlp" or "none". + // Env: OTEL_LOGS_EXPORTER (default: "none") + LogsExporter string + // Propagators specifies the propagators to use, comma-separated. + // Supported values: "tracecontext", "baggage", "jaeger" + // Env: OTEL_PROPAGATORS (default: "tracecontext,baggage") + Propagators string +} + +// OTelConfigFromEnv creates an OTelConfig from environment variables. +// See OTelConfig struct fields for supported environment variables. +func OTelConfigFromEnv() OTelConfig { + serviceName := os.Getenv("OTEL_SERVICE_NAME") + if serviceName == "" { + serviceName = "lfx-v2-auth-service" + } + + serviceVersion := os.Getenv("OTEL_SERVICE_VERSION") + + protocol := os.Getenv("OTEL_EXPORTER_OTLP_PROTOCOL") + if protocol == "" { + protocol = OTelProtocolGRPC + } + + endpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT") + + insecure := os.Getenv("OTEL_EXPORTER_OTLP_INSECURE") == "true" + + tracesExporter := os.Getenv("OTEL_TRACES_EXPORTER") + if tracesExporter == "" { + tracesExporter = OTelExporterNone + } + + metricsExporter := os.Getenv("OTEL_METRICS_EXPORTER") + if metricsExporter == "" { + metricsExporter = OTelExporterNone + } + + logsExporter := os.Getenv("OTEL_LOGS_EXPORTER") + if logsExporter == "" { + logsExporter = OTelExporterNone + } + + tracesSampleRatio := 1.0 + if ratio := os.Getenv("OTEL_TRACES_SAMPLE_RATIO"); ratio != "" { + if parsed, err := strconv.ParseFloat(ratio, 64); err == nil { + if parsed >= 0.0 && parsed <= 1.0 { + tracesSampleRatio = parsed + } else { + slog.Warn("OTEL_TRACES_SAMPLE_RATIO must be between 0.0 and 1.0, using default 1.0", + "provided-value", ratio) + } + } else { + slog.Warn("invalid OTEL_TRACES_SAMPLE_RATIO value, using default 1.0", + "provided-value", ratio, "error", err) + } + } + + propagators := os.Getenv("OTEL_PROPAGATORS") + if propagators == "" { + propagators = "tracecontext,baggage" + } + + slog.With( + "service-name", serviceName, + "version", serviceVersion, + "protocol", protocol, + "endpoint", endpoint, + "insecure", insecure, + "traces-exporter", tracesExporter, + "traces-sample-ratio", tracesSampleRatio, + "metrics-exporter", metricsExporter, + "logs-exporter", logsExporter, + "propagators", propagators, + ).Debug("OTelConfig") + + return OTelConfig{ + ServiceName: serviceName, + ServiceVersion: serviceVersion, + Protocol: protocol, + Endpoint: endpoint, + Insecure: insecure, + TracesExporter: tracesExporter, + TracesSampleRatio: tracesSampleRatio, + MetricsExporter: metricsExporter, + LogsExporter: logsExporter, + Propagators: propagators, + } +} + +// SetupOTelSDK bootstraps the OpenTelemetry pipeline with OTLP exporters. +// If it does not return an error, make sure to call shutdown for proper cleanup. +func SetupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) { + return SetupOTelSDKWithConfig(ctx, OTelConfigFromEnv()) +} + +// SetupOTelSDKWithConfig bootstraps the OpenTelemetry pipeline with the provided configuration. +// If it does not return an error, make sure to call shutdown for proper cleanup. +func SetupOTelSDKWithConfig(ctx context.Context, cfg OTelConfig) (shutdown func(context.Context) error, err error) { + var shutdownFuncs []func(context.Context) error + + // shutdown calls cleanup functions registered via shutdownFuncs. + // The errors from the calls are joined. + // Each registered cleanup will be invoked once. + shutdown = func(ctx context.Context) error { + var err error + for _, fn := range shutdownFuncs { + err = errors.Join(err, fn(ctx)) + } + shutdownFuncs = nil + return err + } + + // handleErr calls shutdown for cleanup and makes sure that all errors are returned. + handleErr := func(inErr error) { + err = errors.Join(inErr, shutdown(ctx)) + } + + // Create resource with service information. + res, err := newResource(cfg) + if err != nil { + handleErr(err) + return + } + + // Set up propagator. + prop := newPropagator(cfg) + otel.SetTextMapPropagator(prop) + + // Set up trace provider if enabled. + if cfg.TracesExporter != OTelExporterNone { + var tracerProvider *trace.TracerProvider + tracerProvider, err = newTraceProvider(ctx, cfg, res) + if err != nil { + handleErr(err) + return + } + shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown) + otel.SetTracerProvider(tracerProvider) + } + + // Set up metrics provider if enabled. + if cfg.MetricsExporter != OTelExporterNone { + var metricsProvider *metric.MeterProvider + metricsProvider, err = newMetricsProvider(ctx, cfg, res) + if err != nil { + handleErr(err) + return + } + shutdownFuncs = append(shutdownFuncs, metricsProvider.Shutdown) + otel.SetMeterProvider(metricsProvider) + } + + // Set up logger provider if enabled. + if cfg.LogsExporter != OTelExporterNone { + var loggerProvider *log.LoggerProvider + loggerProvider, err = newLoggerProvider(ctx, cfg, res) + if err != nil { + handleErr(err) + return + } + shutdownFuncs = append(shutdownFuncs, loggerProvider.Shutdown) + global.SetLoggerProvider(loggerProvider) + } + + return +} + +// newResource creates an OpenTelemetry resource with service name and version attributes. +func newResource(cfg OTelConfig) (*resource.Resource, error) { + return resource.Merge( + resource.Default(), + resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceName(cfg.ServiceName), + semconv.ServiceVersion(cfg.ServiceVersion), + ), + ) +} + +// newPropagator creates a composite text map propagator based on the configured propagators. +// Supported propagators: "tracecontext", "baggage", "jaeger" +func newPropagator(cfg OTelConfig) propagation.TextMapPropagator { + var propagators []propagation.TextMapPropagator + + for _, p := range strings.Split(cfg.Propagators, ",") { + switch strings.TrimSpace(p) { + case "tracecontext": + propagators = append(propagators, propagation.TraceContext{}) + case "baggage": + propagators = append(propagators, propagation.Baggage{}) + case "jaeger": + propagators = append(propagators, jaeger.Jaeger{}) + default: + slog.Warn("unknown propagator, skipping", "propagator", p) + } + } + + if len(propagators) == 0 { + // Fallback to defaults if no valid propagators configured + propagators = []propagation.TextMapPropagator{ + propagation.TraceContext{}, + propagation.Baggage{}, + } + } + + return propagation.NewCompositeTextMapPropagator(propagators...) +} + +// newTraceProvider creates a TracerProvider with an OTLP exporter configured based on the protocol setting. +func newTraceProvider(ctx context.Context, cfg OTelConfig, res *resource.Resource) (*trace.TracerProvider, error) { + var exporter trace.SpanExporter + var err error + + if cfg.Protocol == OTelProtocolHTTP { + opts := []otlptracehttp.Option{} + if cfg.Endpoint != "" { + opts = append(opts, otlptracehttp.WithEndpoint(cfg.Endpoint)) + } + if cfg.Insecure { + opts = append(opts, otlptracehttp.WithInsecure()) + } + exporter, err = otlptracehttp.New(ctx, opts...) + } else { + opts := []otlptracegrpc.Option{} + if cfg.Endpoint != "" { + opts = append(opts, otlptracegrpc.WithEndpoint(cfg.Endpoint)) + } + if cfg.Insecure { + opts = append(opts, otlptracegrpc.WithInsecure()) + } + exporter, err = otlptracegrpc.New(ctx, opts...) + } + + if err != nil { + return nil, err + } + + traceProvider := trace.NewTracerProvider( + trace.WithResource(res), + trace.WithSampler(trace.TraceIDRatioBased(cfg.TracesSampleRatio)), + trace.WithBatcher(exporter, + trace.WithBatchTimeout(time.Second), + ), + ) + return traceProvider, nil +} + +// newMetricsProvider creates a MeterProvider with an OTLP exporter configured based on the protocol setting. +func newMetricsProvider(ctx context.Context, cfg OTelConfig, res *resource.Resource) (*metric.MeterProvider, error) { + var exporter metric.Exporter + var err error + + if cfg.Protocol == OTelProtocolHTTP { + opts := []otlpmetrichttp.Option{} + if cfg.Endpoint != "" { + opts = append(opts, otlpmetrichttp.WithEndpoint(cfg.Endpoint)) + } + if cfg.Insecure { + opts = append(opts, otlpmetrichttp.WithInsecure()) + } + exporter, err = otlpmetrichttp.New(ctx, opts...) + } else { + opts := []otlpmetricgrpc.Option{} + if cfg.Endpoint != "" { + opts = append(opts, otlpmetricgrpc.WithEndpoint(cfg.Endpoint)) + } + if cfg.Insecure { + opts = append(opts, otlpmetricgrpc.WithInsecure()) + } + exporter, err = otlpmetricgrpc.New(ctx, opts...) + } + + if err != nil { + return nil, err + } + + metricsProvider := metric.NewMeterProvider( + metric.WithResource(res), + metric.WithReader(metric.NewPeriodicReader(exporter, + metric.WithInterval(30*time.Second), + )), + ) + return metricsProvider, nil +} + +// newLoggerProvider creates a LoggerProvider with an OTLP exporter configured based on the protocol setting. +func newLoggerProvider(ctx context.Context, cfg OTelConfig, res *resource.Resource) (*log.LoggerProvider, error) { + var exporter log.Exporter + var err error + + if cfg.Protocol == OTelProtocolHTTP { + opts := []otlploghttp.Option{} + if cfg.Endpoint != "" { + opts = append(opts, otlploghttp.WithEndpoint(cfg.Endpoint)) + } + if cfg.Insecure { + opts = append(opts, otlploghttp.WithInsecure()) + } + exporter, err = otlploghttp.New(ctx, opts...) + } else { + opts := []otlploggrpc.Option{} + if cfg.Endpoint != "" { + opts = append(opts, otlploggrpc.WithEndpoint(cfg.Endpoint)) + } + if cfg.Insecure { + opts = append(opts, otlploggrpc.WithInsecure()) + } + exporter, err = otlploggrpc.New(ctx, opts...) + } + + if err != nil { + return nil, err + } + + loggerProvider := log.NewLoggerProvider( + log.WithResource(res), + log.WithProcessor(log.NewBatchProcessor(exporter)), + ) + return loggerProvider, nil +} diff --git a/pkg/utils/otel_test.go b/pkg/utils/otel_test.go new file mode 100644 index 0000000..e8d13ea --- /dev/null +++ b/pkg/utils/otel_test.go @@ -0,0 +1,399 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package utils + +import ( + "context" + "testing" +) + +// TestOTelConfigFromEnv_Defaults verifies that OTelConfigFromEnv returns +// sensible default values when no environment variables are set. +func TestOTelConfigFromEnv_Defaults(t *testing.T) { + cfg := OTelConfigFromEnv() + + if cfg.ServiceName != "lfx-v2-auth-service" { + t.Errorf("expected default ServiceName 'lfx-v2-auth-service', got %q", cfg.ServiceName) + } + if cfg.ServiceVersion != "" { + t.Errorf("expected empty ServiceVersion, got %q", cfg.ServiceVersion) + } + if cfg.Protocol != OTelProtocolGRPC { + t.Errorf("expected default Protocol %q, got %q", OTelProtocolGRPC, cfg.Protocol) + } + if cfg.Endpoint != "" { + t.Errorf("expected empty Endpoint, got %q", cfg.Endpoint) + } + if cfg.Insecure != false { + t.Errorf("expected Insecure false, got %t", cfg.Insecure) + } + if cfg.TracesExporter != OTelExporterNone { + t.Errorf("expected default TracesExporter %q, got %q", OTelExporterNone, cfg.TracesExporter) + } + if cfg.TracesSampleRatio != 1.0 { + t.Errorf("expected default TracesSampleRatio 1.0, got %f", cfg.TracesSampleRatio) + } + if cfg.MetricsExporter != OTelExporterNone { + t.Errorf("expected default MetricsExporter %q, got %q", OTelExporterNone, cfg.MetricsExporter) + } + if cfg.LogsExporter != OTelExporterNone { + t.Errorf("expected default LogsExporter %q, got %q", OTelExporterNone, cfg.LogsExporter) + } +} + +// TestOTelConfigFromEnv_CustomValues verifies that OTelConfigFromEnv correctly +// reads and parses all supported OTEL_* environment variables. +func TestOTelConfigFromEnv_CustomValues(t *testing.T) { + t.Setenv("OTEL_SERVICE_NAME", "test-service") + t.Setenv("OTEL_SERVICE_VERSION", "1.2.3") + t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "http") + t.Setenv("OTEL_EXPORTER_OTLP_ENDPOINT", "localhost:4318") + t.Setenv("OTEL_EXPORTER_OTLP_INSECURE", "true") + t.Setenv("OTEL_TRACES_EXPORTER", "otlp") + t.Setenv("OTEL_TRACES_SAMPLE_RATIO", "0.5") + t.Setenv("OTEL_METRICS_EXPORTER", "otlp") + t.Setenv("OTEL_LOGS_EXPORTER", "otlp") + + cfg := OTelConfigFromEnv() + + if cfg.ServiceName != "test-service" { + t.Errorf("expected ServiceName 'test-service', got %q", cfg.ServiceName) + } + if cfg.ServiceVersion != "1.2.3" { + t.Errorf("expected ServiceVersion '1.2.3', got %q", cfg.ServiceVersion) + } + if cfg.Protocol != OTelProtocolHTTP { + t.Errorf("expected Protocol %q, got %q", OTelProtocolHTTP, cfg.Protocol) + } + if cfg.Endpoint != "localhost:4318" { + t.Errorf("expected Endpoint 'localhost:4318', got %q", cfg.Endpoint) + } + if cfg.Insecure != true { + t.Errorf("expected Insecure true, got %t", cfg.Insecure) + } + if cfg.TracesExporter != OTelExporterOTLP { + t.Errorf("expected TracesExporter %q, got %q", OTelExporterOTLP, cfg.TracesExporter) + } + if cfg.TracesSampleRatio != 0.5 { + t.Errorf("expected TracesSampleRatio 0.5, got %f", cfg.TracesSampleRatio) + } + if cfg.MetricsExporter != OTelExporterOTLP { + t.Errorf("expected MetricsExporter %q, got %q", OTelExporterOTLP, cfg.MetricsExporter) + } + if cfg.LogsExporter != OTelExporterOTLP { + t.Errorf("expected LogsExporter %q, got %q", OTelExporterOTLP, cfg.LogsExporter) + } +} + +// TestOTelConfigFromEnv_UnsupportedProtocol verifies that an unsupported protocol +// value is passed through as-is (defaults to gRPC behavior in the provider functions). +func TestOTelConfigFromEnv_UnsupportedProtocol(t *testing.T) { + t.Setenv("OTEL_EXPORTER_OTLP_PROTOCOL", "unsupported") + + cfg := OTelConfigFromEnv() + + if cfg.Protocol != "unsupported" { + t.Errorf("expected Protocol 'unsupported', got %q", cfg.Protocol) + } +} + +// TestOTelConfigFromEnv_TracesSampleRatio tests the parsing and validation of +// the OTEL_TRACES_SAMPLE_RATIO environment variable, including edge cases like +// invalid values, out-of-range numbers, and empty strings. +func TestOTelConfigFromEnv_TracesSampleRatio(t *testing.T) { + tests := []struct { + name string + envValue string + expectedRatio float64 + }{ + {"valid zero", "0.0", 0.0}, + {"valid half", "0.5", 0.5}, + {"valid one", "1.0", 1.0}, + {"valid small", "0.01", 0.01}, + {"invalid negative", "-0.5", 1.0}, // defaults to 1.0 + {"invalid above one", "1.5", 1.0}, // defaults to 1.0 + {"invalid non-number", "invalid", 1.0}, // defaults to 1.0 + {"empty string", "", 1.0}, // defaults to 1.0 + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.envValue != "" { + t.Setenv("OTEL_TRACES_SAMPLE_RATIO", tt.envValue) + } + + cfg := OTelConfigFromEnv() + + if cfg.TracesSampleRatio != tt.expectedRatio { + t.Errorf("expected TracesSampleRatio %f, got %f", tt.expectedRatio, cfg.TracesSampleRatio) + } + }) + } +} + +// TestOTelConfigFromEnv_InsecureFlag tests the parsing of the +// OTEL_EXPORTER_OTLP_INSECURE environment variable. Only the literal string +// "true" enables insecure mode; all other values default to false. +func TestOTelConfigFromEnv_InsecureFlag(t *testing.T) { + tests := []struct { + name string + envValue string + expected bool + }{ + {"true", "true", true}, + {"false", "false", false}, + {"empty", "", false}, + {"TRUE uppercase", "TRUE", false}, // only "true" is recognized + {"1", "1", false}, // only "true" is recognized + {"yes", "yes", false}, // only "true" is recognized + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.envValue != "" { + t.Setenv("OTEL_EXPORTER_OTLP_INSECURE", tt.envValue) + } + + cfg := OTelConfigFromEnv() + + if cfg.Insecure != tt.expected { + t.Errorf("expected Insecure %t, got %t", tt.expected, cfg.Insecure) + } + }) + } +} + +// TestSetupOTelSDKWithConfig_AllDisabled verifies that the SDK can be +// initialized successfully when all exporters (traces, metrics, logs) are +// disabled, and that the returned shutdown function works correctly. +func TestSetupOTelSDKWithConfig_AllDisabled(t *testing.T) { + cfg := OTelConfig{ + ServiceName: "test-service", + ServiceVersion: "1.0.0", + Protocol: OTelProtocolGRPC, + TracesExporter: OTelExporterNone, + TracesSampleRatio: 1.0, + MetricsExporter: OTelExporterNone, + LogsExporter: OTelExporterNone, + } + + ctx := context.Background() + shutdown, err := SetupOTelSDKWithConfig(ctx, cfg) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if shutdown == nil { + t.Fatal("expected non-nil shutdown function") + } + + // Call shutdown to ensure it works without error + err = shutdown(ctx) + if err != nil { + t.Errorf("shutdown returned unexpected error: %v", err) + } +} + +// TestSetupOTelSDKWithConfig_ShutdownIdempotent verifies that the shutdown +// function can be called multiple times without error. This is important for +// graceful shutdown scenarios where shutdown may be triggered multiple times. +func TestSetupOTelSDKWithConfig_ShutdownIdempotent(t *testing.T) { + cfg := OTelConfig{ + ServiceName: "test-service", + ServiceVersion: "1.0.0", + Protocol: OTelProtocolGRPC, + TracesExporter: OTelExporterNone, + TracesSampleRatio: 1.0, + MetricsExporter: OTelExporterNone, + LogsExporter: OTelExporterNone, + } + + ctx := context.Background() + shutdown, err := SetupOTelSDKWithConfig(ctx, cfg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + // Call shutdown multiple times + err = shutdown(ctx) + if err != nil { + t.Errorf("first shutdown returned unexpected error: %v", err) + } + + // Second call should also succeed (shutdownFuncs is cleared) + err = shutdown(ctx) + if err != nil { + t.Errorf("second shutdown returned unexpected error: %v", err) + } +} + +// TestNewResource verifies that newResource creates a valid OpenTelemetry +// resource with the expected service.name attribute for various input values, +// including edge cases like empty versions and unicode characters. +func TestNewResource(t *testing.T) { + tests := []struct { + name string + serviceName string + serviceVersion string + }{ + {"basic", "test-service", "1.0.0"}, + {"empty version", "test-service", ""}, + {"unicode name", "测试服务", "2.0.0"}, + {"special chars", "test-service-123", "1.0.0-beta.1"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := OTelConfig{ + ServiceName: tt.serviceName, + ServiceVersion: tt.serviceVersion, + } + + res, err := newResource(cfg) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if res == nil { + t.Fatal("expected non-nil resource") + } + + // Verify resource contains expected attributes + attrs := res.Attributes() + found := false + for _, attr := range attrs { + if string(attr.Key) == "service.name" && attr.Value.AsString() == tt.serviceName { + found = true + break + } + } + if !found { + t.Errorf("resource missing service.name attribute with value %q", tt.serviceName) + } + }) + } +} + +// TestNewPropagator verifies that newPropagator returns a composite +// TextMapPropagator that includes the standard W3C trace context fields +// (traceparent, tracestate) and baggage propagation. +func TestNewPropagator(t *testing.T) { + cfg := OTelConfig{Propagators: "tracecontext,baggage"} + prop := newPropagator(cfg) + + if prop == nil { + t.Fatal("expected non-nil propagator") + } + + // Verify it's a composite propagator with expected fields + fields := prop.Fields() + if len(fields) == 0 { + t.Error("expected propagator to have fields") + } + + // Check for expected propagation fields (traceparent, tracestate, baggage) + expectedFields := map[string]bool{ + "traceparent": false, + "tracestate": false, + "baggage": false, + } + + for _, field := range fields { + expectedFields[field] = true + } + + for field, found := range expectedFields { + if !found { + t.Errorf("expected propagator to include field %q", field) + } + } +} + +// TestOTelConstants verifies that the exported OTel constants have their +// expected string values, ensuring API compatibility. +func TestOTelConstants(t *testing.T) { + // Verify constants have expected values + if OTelProtocolGRPC != "grpc" { + t.Errorf("expected OTelProtocolGRPC to be 'grpc', got %q", OTelProtocolGRPC) + } + if OTelProtocolHTTP != "http" { + t.Errorf("expected OTelProtocolHTTP to be 'http', got %q", OTelProtocolHTTP) + } + if OTelExporterOTLP != "otlp" { + t.Errorf("expected OTelExporterOTLP to be 'otlp', got %q", OTelExporterOTLP) + } + if OTelExporterNone != "none" { + t.Errorf("expected OTelExporterNone to be 'none', got %q", OTelExporterNone) + } +} + +// TestSetupOTelSDK tests the convenience function SetupOTelSDK which reads +// configuration from environment variables. With no env vars set, it should +// use defaults and successfully initialize the SDK. +func TestSetupOTelSDK(t *testing.T) { + ctx := context.Background() + shutdown, err := SetupOTelSDK(ctx) + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if shutdown == nil { + t.Fatal("expected non-nil shutdown function") + } + + err = shutdown(ctx) + if err != nil { + t.Errorf("shutdown returned unexpected error: %v", err) + } +} + +// TestOTelConfig_ZeroValue documents that a zero-value OTelConfig does not +// have exporters disabled by default. Users must explicitly set exporters to +// OTelExporterNone to disable them. +func TestOTelConfig_ZeroValue(t *testing.T) { + // Test that zero-value config (with empty strings) tries to enable exporters + // because empty string != "none". This verifies the expected behavior that + // users should explicitly set exporters to "none" to disable them. + cfg := OTelConfig{} + + // Verify the zero-value behavior: empty string fields mean exporters would be enabled + if cfg.TracesExporter == OTelExporterNone { + t.Error("expected zero-value TracesExporter to NOT equal OTelExporterNone") + } + if cfg.MetricsExporter == OTelExporterNone { + t.Error("expected zero-value MetricsExporter to NOT equal OTelExporterNone") + } + if cfg.LogsExporter == OTelExporterNone { + t.Error("expected zero-value LogsExporter to NOT equal OTelExporterNone") + } +} + +// TestOTelConfig_MinimalConfig verifies that the SDK can be initialized with +// a minimal configuration where only the exporter settings are specified. +func TestOTelConfig_MinimalConfig(t *testing.T) { + // Test minimal config with all exporters explicitly disabled + cfg := OTelConfig{ + TracesExporter: OTelExporterNone, + MetricsExporter: OTelExporterNone, + LogsExporter: OTelExporterNone, + } + + ctx := context.Background() + shutdown, err := SetupOTelSDKWithConfig(ctx, cfg) + + if err != nil { + t.Fatalf("unexpected error with minimal config: %v", err) + } + + if shutdown == nil { + t.Fatal("expected non-nil shutdown function") + } + + err = shutdown(ctx) + if err != nil { + t.Errorf("shutdown returned unexpected error: %v", err) + } +} From 432e1bbf9bf1a1c7d8eb23bf16f4511a0d151ba6 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Mon, 2 Feb 2026 09:38:11 -0800 Subject: [PATCH 02/14] feat: add jaeger to default OTEL propagators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Include jaeger propagator in default propagators alongside tracecontext and baggage for better trace context propagation compatibility. Update helm deployment template to only set OTEL env vars when endpoint is configured. Also updates OpenTelemetry and related dependencies to latest versions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- .../templates/deployment.yaml | 22 ++--- go.mod | 49 +++++----- go.sum | 97 ++++++++++--------- pkg/utils/otel.go | 5 +- pkg/utils/otel_test.go | 38 ++++++-- 5 files changed, 123 insertions(+), 88 deletions(-) diff --git a/charts/lfx-v2-auth-service/templates/deployment.yaml b/charts/lfx-v2-auth-service/templates/deployment.yaml index 12126ea..532f7bb 100644 --- a/charts/lfx-v2-auth-service/templates/deployment.yaml +++ b/charts/lfx-v2-auth-service/templates/deployment.yaml @@ -38,46 +38,46 @@ spec: {{- with .Values.app.extraEnv }} {{- toYaml . | nindent 10 }} {{- end }} - {{- if .Values.app.otel.serviceName }} + {{- if ne .Values.app.otel.endpoint "" }} + {{- if ne .Values.app.otel.serviceName "" }} - name: OTEL_SERVICE_NAME value: {{ .Values.app.otel.serviceName | quote }} {{- end }} - {{- if .Values.app.otel.serviceVersion }} + {{- if ne .Values.app.otel.serviceVersion "" }} - name: OTEL_SERVICE_VERSION value: {{ .Values.app.otel.serviceVersion | quote }} {{- end }} - {{- if .Values.app.otel.endpoint }} - name: OTEL_EXPORTER_OTLP_ENDPOINT value: {{ .Values.app.otel.endpoint | quote }} - {{- end }} - {{- if .Values.app.otel.protocol }} + {{- if ne .Values.app.otel.protocol "" }} - name: OTEL_EXPORTER_OTLP_PROTOCOL value: {{ .Values.app.otel.protocol | quote }} {{- end }} - {{- if .Values.app.otel.insecure }} + {{- if ne .Values.app.otel.insecure "" }} - name: OTEL_EXPORTER_OTLP_INSECURE value: {{ .Values.app.otel.insecure | quote }} {{- end }} - {{- if .Values.app.otel.tracesExporter }} + {{- if ne .Values.app.otel.tracesExporter "" }} - name: OTEL_TRACES_EXPORTER value: {{ .Values.app.otel.tracesExporter | quote }} {{- end }} - {{- if .Values.app.otel.tracesSampleRatio }} + {{- if ne .Values.app.otel.tracesSampleRatio "" }} - name: OTEL_TRACES_SAMPLE_RATIO value: {{ .Values.app.otel.tracesSampleRatio | quote }} {{- end }} - {{- if .Values.app.otel.metricsExporter }} + {{- if ne .Values.app.otel.metricsExporter "" }} - name: OTEL_METRICS_EXPORTER value: {{ .Values.app.otel.metricsExporter | quote }} {{- end }} - {{- if .Values.app.otel.logsExporter }} + {{- if ne .Values.app.otel.logsExporter "" }} - name: OTEL_LOGS_EXPORTER value: {{ .Values.app.otel.logsExporter | quote }} {{- end }} - {{- if .Values.app.otel.propagators }} + {{- if ne .Values.app.otel.propagators "" }} - name: OTEL_PROPAGATORS value: {{ .Values.app.otel.propagators | quote }} {{- end }} + {{- end }} ports: - containerPort: {{ .Values.service.port }} name: web diff --git a/go.mod b/go.mod index 941a87d..31976f5 100644 --- a/go.mod +++ b/go.mod @@ -13,15 +13,15 @@ require ( github.com/nats-io/nats.go v1.45.0 github.com/remychantenay/slog-otel v1.3.4 github.com/stretchr/testify v1.11.1 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 go.opentelemetry.io/otel v1.39.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 go.opentelemetry.io/otel/log v0.15.0 go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/log v0.15.0 @@ -30,20 +30,21 @@ require ( go.yaml.in/yaml/v2 v2.4.2 goa.design/clue v1.2.3 goa.design/goa/v3 v3.23.3 - golang.org/x/crypto v0.45.0 - golang.org/x/oauth2 v0.32.0 - golang.org/x/sync v0.18.0 + golang.org/x/crypto v0.47.0 + golang.org/x/oauth2 v0.34.0 + golang.org/x/sync v0.19.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.34.1 k8s.io/client-go v0.34.1 ) require ( + github.com/DataDog/dd-trace-go/v2 v2.5.0 // indirect github.com/PuerkitoBio/rehttp v1.4.0 // indirect github.com/aws/smithy-go v1.23.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect @@ -60,7 +61,7 @@ require ( github.com/gohugoio/hashstructure v0.6.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect @@ -77,27 +78,29 @@ require ( github.com/nats-io/nkeys v0.4.11 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/segmentio/asm v1.2.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/x448/float16 v0.8.4 // indirect go.devnw.com/structs v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect + go.opentelemetry.io/otel/sdk/log/logtest v0.15.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.30.0 // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.39.0 // indirect - golang.org/x/term v0.37.0 // indirect - golang.org/x/text v0.31.0 // indirect - golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.39.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.12.0 // indirect + golang.org/x/tools v0.40.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/grpc v1.78.0 // 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 k8s.io/api v0.34.1 // indirect diff --git a/go.sum b/go.sum index 7ddeb46..9ac741b 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/DataDog/dd-trace-go/v2 v2.5.0 h1:Tp4McT135WhbdT/6BYcAoRvl5gH7YKzehSo6Q3uuxBM= +github.com/DataDog/dd-trace-go/v2 v2.5.0/go.mod h1:A9rVmQfyzYUFCctFdKkli9us7G/YhXlMICpQ958wJUA= github.com/PuerkitoBio/rehttp v1.4.0 h1:rIN7A2s+O9fmHUM1vUcInvlHj9Ysql4hE+Y0wcl/xk8= github.com/PuerkitoBio/rehttp v1.4.0/go.mod h1:LUwKPoDbDIA2RL5wYZCNsQ90cx4OJ4AWBmq6KzWZL1s= github.com/auth0/go-auth0 v1.28.0 h1:yJULZamgYW95sxbAkSwQl9Q5n05XPxdxQ/wZRp5E7fY= @@ -14,8 +16,9 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI= @@ -65,8 +68,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 h1:1ufTZkFXIQQ9EmgPjcIPIi2krfxG03lQ8OLoY1MJ3UM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -122,8 +125,9 @@ 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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/remychantenay/slog-otel v1.3.4 h1:xoM41ayLff2U8zlK5PH31XwD7Lk3W9wKfl4+RcmKom4= github.com/remychantenay/slog-otel v1.3.4/go.mod h1:ZkazuFMICKGDrO0r1njxKRdjTt/YcXKn6v2+0q/b0+U= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -153,8 +157,8 @@ go.devnw.com/structs v1.0.0 h1:FFkBoBOkapCdxFEIkpOZRmMOMr9b9hxjKTD3bJYl9lk= go.devnw.com/structs v1.0.0/go.mod h1:wHBkdQpNeazdQHszJ2sxwVEpd8zGTEsKkeywDLGbrmg= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/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/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 h1:Gz3yKzfMSEFzF0Vy5eIpu9ndpo4DhXMCxsLMF0OOApo= go.opentelemetry.io/contrib/propagators/jaeger v1.39.0/go.mod h1:2D/cxxCqTlrday0rZrPujjg5aoAdqk1NaNyoXn8FJn8= go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= @@ -163,18 +167,18 @@ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0/go.mod h1:JM31r0GGZ/GU94mX8hN4D8v6e40aFlUECSQ48HaLgHM= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 h1:EKpiGphOYq3CYnIe2eX9ftUkyU+Y8Dtte8OaWyHJ4+I= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0/go.mod h1:nWFP7C+T8TygkTjJ7mAyEaFaE7wNfms3nV/vexZ6qt0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0 h1:kJxSDN4SgWWTjG/hPp3O7LCGLcHXFlvS2/FFOrwL+SE= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.38.0/go.mod h1:mgIOzS7iZeKJdeB8/NYHrJ48fdGc71Llo5bJ1J4DWUE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o= +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/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= +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/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= @@ -183,8 +187,8 @@ go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKz go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= -go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= -go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= +go.opentelemetry.io/otel/sdk/log/logtest v0.15.0 h1:O+dZyt9riqVDKZwFRFn9zVdUKam3uwLMud+poHRssd4= +go.opentelemetry.io/otel/sdk/log/logtest v0.15.0/go.mod h1:j7aD3tWSt3KlzWz5G+9loktEng6udEY868Kc2XDzdII= go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= @@ -204,63 +208,64 @@ goa.design/goa/v3 v3.23.3/go.mod h1:DaJ9yv5WoXrpolbzouDj0A0o5Os0rPTTHy4aSebYVuI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= -golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= -golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= -golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= -golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +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/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/pkg/utils/otel.go b/pkg/utils/otel.go index 69155e9..91e0fb3 100644 --- a/pkg/utils/otel.go +++ b/pkg/utils/otel.go @@ -75,7 +75,7 @@ type OTelConfig struct { LogsExporter string // Propagators specifies the propagators to use, comma-separated. // Supported values: "tracecontext", "baggage", "jaeger" - // Env: OTEL_PROPAGATORS (default: "tracecontext,baggage") + // Env: OTEL_PROPAGATORS (default: "tracecontext,baggage,jaeger") Propagators string } @@ -130,7 +130,7 @@ func OTelConfigFromEnv() OTelConfig { propagators := os.Getenv("OTEL_PROPAGATORS") if propagators == "" { - propagators = "tracecontext,baggage" + propagators = "tracecontext,baggage,jaeger" } slog.With( @@ -273,6 +273,7 @@ func newPropagator(cfg OTelConfig) propagation.TextMapPropagator { propagators = []propagation.TextMapPropagator{ propagation.TraceContext{}, propagation.Baggage{}, + jaeger.Jaeger{}, } } diff --git a/pkg/utils/otel_test.go b/pkg/utils/otel_test.go index e8d13ea..a76f269 100644 --- a/pkg/utils/otel_test.go +++ b/pkg/utils/otel_test.go @@ -278,9 +278,9 @@ func TestNewResource(t *testing.T) { // TestNewPropagator verifies that newPropagator returns a composite // TextMapPropagator that includes the standard W3C trace context fields -// (traceparent, tracestate) and baggage propagation. +// (traceparent, tracestate), baggage propagation, and jaeger propagation. func TestNewPropagator(t *testing.T) { - cfg := OTelConfig{Propagators: "tracecontext,baggage"} + cfg := OTelConfig{Propagators: "tracecontext,baggage,jaeger"} prop := newPropagator(cfg) if prop == nil { @@ -293,11 +293,12 @@ func TestNewPropagator(t *testing.T) { t.Error("expected propagator to have fields") } - // Check for expected propagation fields (traceparent, tracestate, baggage) + // Check for expected propagation fields (traceparent, tracestate, baggage, uber-trace-id) expectedFields := map[string]bool{ - "traceparent": false, - "tracestate": false, - "baggage": false, + "traceparent": false, + "tracestate": false, + "baggage": false, + "uber-trace-id": false, // jaeger propagator header } for _, field := range fields { @@ -311,6 +312,31 @@ func TestNewPropagator(t *testing.T) { } } +// TestNewPropagator_Default verifies that the default propagators include +// tracecontext, baggage, and jaeger when no OTEL_PROPAGATORS is set. +func TestNewPropagator_Default(t *testing.T) { + cfg := OTelConfigFromEnv() + + if cfg.Propagators != "tracecontext,baggage,jaeger" { + t.Errorf("expected default Propagators to be 'tracecontext,baggage,jaeger', got %q", cfg.Propagators) + } + + prop := newPropagator(cfg) + fields := prop.Fields() + + // Should include uber-trace-id from jaeger propagator + hasJaeger := false + for _, field := range fields { + if field == "uber-trace-id" { + hasJaeger = true + break + } + } + if !hasJaeger { + t.Error("expected default propagator to include jaeger (uber-trace-id field)") + } +} + // TestOTelConstants verifies that the exported OTel constants have their // expected string values, ensuring API compatibility. func TestOTelConstants(t *testing.T) { From 461132b821f820f34407167dddeb3743b2d82551 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Mon, 2 Feb 2026 13:12:59 -0800 Subject: [PATCH 03/14] Add OTEL build-time variables and shutdown timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Version, BuildTime, GitCommit build-time variables - Update OTEL shutdown to use context with timeout - Add ldflags to ko-build workflows for version injection - ServiceVersion falls back to build-time Version if env var empty 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- .github/workflows/ko-build-branch.yaml | 7 ++- .github/workflows/ko-build-main.yaml | 10 +++- .github/workflows/ko-build-tag.yaml | 7 ++- .../templates/deployment.yaml | 52 +++++++++++-------- cmd/server/main.go | 16 +++++- go.mod | 1 - go.sum | 5 +- 7 files changed, 67 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ko-build-branch.yaml b/.github/workflows/ko-build-branch.yaml index aee3880..e5678df 100644 --- a/.github/workflows/ko-build-branch.yaml +++ b/.github/workflows/ko-build-branch.yaml @@ -35,10 +35,15 @@ jobs: container_tag=$(echo "$HEAD_REF" | sed 's/[^_0-9a-zA-Z]/-/g' | cut -c -127) echo tag="$container_tag" >> "$GITHUB_OUTPUT" - name: Build auth-service for PR + env: + VERSION: ${{ steps.container_tag.outputs.tag }} + GIT_COMMIT: ${{ github.sha }} run: | + BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export GOFLAGS="-ldflags=-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT:0:7}" ko build github.com/linuxfoundation/lfx-v2-auth-service/cmd/server \ -B \ --platform linux/amd64,linux/arm64 \ -t ${{ github.sha }} \ -t ${{ steps.container_tag.outputs.tag }} \ - --sbom spdx + --sbom spdx \ diff --git a/.github/workflows/ko-build-main.yaml b/.github/workflows/ko-build-main.yaml index 3d5db67..7a55622 100644 --- a/.github/workflows/ko-build-main.yaml +++ b/.github/workflows/ko-build-main.yaml @@ -27,10 +27,16 @@ jobs: - uses: ko-build/setup-ko@v0.8 with: version: v0.17.1 - - run: | + - name: Build and publish auth-service image + env: + VERSION: development + GIT_COMMIT: ${{ github.sha }} + run: | + BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export GOFLAGS="-ldflags=-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT:0:7}" ko build github.com/linuxfoundation/lfx-v2-auth-service/cmd/server \ -B \ --platform linux/amd64,linux/arm64 \ -t ${{ github.sha }} \ -t development \ - --sbom spdx + --sbom spdx \ diff --git a/.github/workflows/ko-build-tag.yaml b/.github/workflows/ko-build-tag.yaml index dd7be5f..3e62f70 100644 --- a/.github/workflows/ko-build-tag.yaml +++ b/.github/workflows/ko-build-tag.yaml @@ -54,14 +54,19 @@ jobs: version: v0.17.1 - name: Build and publish auth-service image + env: + VERSION: ${{ steps.prepare.outputs.app_version }} + GIT_COMMIT: ${{ github.sha }} run: | + BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export GOFLAGS="-ldflags=-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT:0:7}" ko build github.com/linuxfoundation/lfx-v2-auth-service/cmd/server \ -B \ --platform linux/amd64,linux/arm64 \ -t ${{ github.ref_name }} \ -t ${{ steps.prepare.outputs.app_version }} \ -t latest \ - --sbom spdx + --sbom spdx \ release-helm-chart: needs: publish diff --git a/charts/lfx-v2-auth-service/templates/deployment.yaml b/charts/lfx-v2-auth-service/templates/deployment.yaml index 532f7bb..0726cfa 100644 --- a/charts/lfx-v2-auth-service/templates/deployment.yaml +++ b/charts/lfx-v2-auth-service/templates/deployment.yaml @@ -38,45 +38,55 @@ spec: {{- with .Values.app.extraEnv }} {{- toYaml . | nindent 10 }} {{- end }} - {{- if ne .Values.app.otel.endpoint "" }} - {{- if ne .Values.app.otel.serviceName "" }} + {{- $otelServiceName := .Values.app.otel.serviceName | toString | trim }} + {{- if ne $otelServiceName "" }} - name: OTEL_SERVICE_NAME - value: {{ .Values.app.otel.serviceName | quote }} + value: {{ $otelServiceName | quote }} {{- end }} - {{- if ne .Values.app.otel.serviceVersion "" }} + {{- $otelServiceVersion := .Values.app.otel.serviceVersion | toString | trim }} + {{- if ne $otelServiceVersion "" }} - name: OTEL_SERVICE_VERSION - value: {{ .Values.app.otel.serviceVersion | quote }} + value: {{ $otelServiceVersion | quote }} {{- end }} + {{- $otelEndpoint := .Values.app.otel.endpoint | toString | trim }} + {{- if ne $otelEndpoint "" }} - name: OTEL_EXPORTER_OTLP_ENDPOINT - value: {{ .Values.app.otel.endpoint | quote }} - {{- if ne .Values.app.otel.protocol "" }} + value: {{ $otelEndpoint | quote }} + {{- end }} + {{- $otelProtocol := .Values.app.otel.protocol | toString | trim }} + {{- if ne $otelProtocol "" }} - name: OTEL_EXPORTER_OTLP_PROTOCOL - value: {{ .Values.app.otel.protocol | quote }} + value: {{ $otelProtocol | quote }} {{- end }} - {{- if ne .Values.app.otel.insecure "" }} + {{- $otelInsecure := .Values.app.otel.insecure | toString | trim }} + {{- if ne $otelInsecure "" }} - name: OTEL_EXPORTER_OTLP_INSECURE - value: {{ .Values.app.otel.insecure | quote }} + value: {{ $otelInsecure | quote }} {{- end }} - {{- if ne .Values.app.otel.tracesExporter "" }} + {{- $otelTracesExporter := .Values.app.otel.tracesExporter | toString | trim }} + {{- if ne $otelTracesExporter "" }} - name: OTEL_TRACES_EXPORTER - value: {{ .Values.app.otel.tracesExporter | quote }} + value: {{ $otelTracesExporter | quote }} {{- end }} - {{- if ne .Values.app.otel.tracesSampleRatio "" }} + {{- $otelTracesSampleRatio := .Values.app.otel.tracesSampleRatio | toString | trim }} + {{- if ne $otelTracesSampleRatio "" }} - name: OTEL_TRACES_SAMPLE_RATIO - value: {{ .Values.app.otel.tracesSampleRatio | quote }} + value: {{ $otelTracesSampleRatio | quote }} {{- end }} - {{- if ne .Values.app.otel.metricsExporter "" }} + {{- $otelMetricsExporter := .Values.app.otel.metricsExporter | toString | trim }} + {{- if ne $otelMetricsExporter "" }} - name: OTEL_METRICS_EXPORTER - value: {{ .Values.app.otel.metricsExporter | quote }} + value: {{ $otelMetricsExporter | quote }} {{- end }} - {{- if ne .Values.app.otel.logsExporter "" }} + {{- $otelLogsExporter := .Values.app.otel.logsExporter | toString | trim }} + {{- if ne $otelLogsExporter "" }} - name: OTEL_LOGS_EXPORTER - value: {{ .Values.app.otel.logsExporter | quote }} + value: {{ $otelLogsExporter | quote }} {{- end }} - {{- if ne .Values.app.otel.propagators "" }} + {{- $otelPropagators := .Values.app.otel.propagators | toString | trim }} + {{- if ne $otelPropagators "" }} - name: OTEL_PROPAGATORS - value: {{ .Values.app.otel.propagators | quote }} - {{- end }} + value: {{ $otelPropagators | quote }} {{- end }} ports: - containerPort: {{ .Values.service.port }} diff --git a/cmd/server/main.go b/cmd/server/main.go index 4fe9985..e445ad9 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -21,6 +21,13 @@ import ( "github.com/linuxfoundation/lfx-v2-auth-service/pkg/utils" ) +// Build-time variables set via ldflags +var ( + Version = "dev" + BuildTime = "unknown" + GitCommit = "unknown" +) + const ( defaultPort = "8080" // gracefulShutdownSeconds should be higher than NATS client @@ -50,7 +57,12 @@ func main() { ctx := context.Background() // Set up OpenTelemetry SDK. + // Command-line/environment OTEL_SERVICE_VERSION takes precedence over + // the build-time Version variable. otelConfig := utils.OTelConfigFromEnv() + if otelConfig.ServiceVersion == "" { + otelConfig.ServiceVersion = Version + } otelShutdown, err := utils.SetupOTelSDKWithConfig(ctx, otelConfig) if err != nil { slog.ErrorContext(ctx, "error setting up OpenTelemetry SDK", "error", err) @@ -58,7 +70,9 @@ func main() { } // Handle shutdown properly so nothing leaks. defer func() { - if shutdownErr := otelShutdown(context.Background()); shutdownErr != nil { + shutdownCtx, cancel := context.WithTimeout(context.Background(), gracefulShutdownSeconds*time.Second) + defer cancel() + if shutdownErr := otelShutdown(shutdownCtx); shutdownErr != nil { slog.ErrorContext(ctx, "error shutting down OpenTelemetry SDK", "error", shutdownErr) } }() diff --git a/go.mod b/go.mod index 31976f5..26a172c 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,6 @@ require ( ) require ( - github.com/DataDog/dd-trace-go/v2 v2.5.0 // indirect github.com/PuerkitoBio/rehttp v1.4.0 // indirect github.com/aws/smithy-go v1.23.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect diff --git a/go.sum b/go.sum index 9ac741b..93f20e5 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/DataDog/dd-trace-go/v2 v2.5.0 h1:Tp4McT135WhbdT/6BYcAoRvl5gH7YKzehSo6Q3uuxBM= -github.com/DataDog/dd-trace-go/v2 v2.5.0/go.mod h1:A9rVmQfyzYUFCctFdKkli9us7G/YhXlMICpQ958wJUA= github.com/PuerkitoBio/rehttp v1.4.0 h1:rIN7A2s+O9fmHUM1vUcInvlHj9Ysql4hE+Y0wcl/xk8= github.com/PuerkitoBio/rehttp v1.4.0/go.mod h1:LUwKPoDbDIA2RL5wYZCNsQ90cx4OJ4AWBmq6KzWZL1s= github.com/auth0/go-auth0 v1.28.0 h1:yJULZamgYW95sxbAkSwQl9Q5n05XPxdxQ/wZRp5E7fY= @@ -243,8 +241,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= From 7cc225e502ff0893f92912a51b141c8d52d66133 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Tue, 3 Feb 2026 09:38:17 -0800 Subject: [PATCH 04/14] Fix GOFLAGS ldflags parsing in workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GOFLAGS parses values as space-separated flags. Use separate -ldflags entries with = syntax for each -X flag. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- .github/workflows/ko-build-branch.yaml | 4 ++-- .github/workflows/ko-build-main.yaml | 4 ++-- .github/workflows/ko-build-tag.yaml | 4 ++-- .ko.yaml | 9 +++++++++ 4 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 .ko.yaml diff --git a/.github/workflows/ko-build-branch.yaml b/.github/workflows/ko-build-branch.yaml index e5678df..7bf41e7 100644 --- a/.github/workflows/ko-build-branch.yaml +++ b/.github/workflows/ko-build-branch.yaml @@ -39,8 +39,8 @@ jobs: VERSION: ${{ steps.container_tag.outputs.tag }} GIT_COMMIT: ${{ github.sha }} run: | - BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') - export GOFLAGS="-ldflags=-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT:0:7}" + export BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export GIT_COMMIT=${GIT_COMMIT:0:7} ko build github.com/linuxfoundation/lfx-v2-auth-service/cmd/server \ -B \ --platform linux/amd64,linux/arm64 \ diff --git a/.github/workflows/ko-build-main.yaml b/.github/workflows/ko-build-main.yaml index 7a55622..8bade5d 100644 --- a/.github/workflows/ko-build-main.yaml +++ b/.github/workflows/ko-build-main.yaml @@ -32,8 +32,8 @@ jobs: VERSION: development GIT_COMMIT: ${{ github.sha }} run: | - BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') - export GOFLAGS="-ldflags=-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT:0:7}" + export BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export GIT_COMMIT=${GIT_COMMIT:0:7} ko build github.com/linuxfoundation/lfx-v2-auth-service/cmd/server \ -B \ --platform linux/amd64,linux/arm64 \ diff --git a/.github/workflows/ko-build-tag.yaml b/.github/workflows/ko-build-tag.yaml index 3e62f70..8229138 100644 --- a/.github/workflows/ko-build-tag.yaml +++ b/.github/workflows/ko-build-tag.yaml @@ -58,8 +58,8 @@ jobs: VERSION: ${{ steps.prepare.outputs.app_version }} GIT_COMMIT: ${{ github.sha }} run: | - BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') - export GOFLAGS="-ldflags=-X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT:0:7}" + export BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export GIT_COMMIT=${GIT_COMMIT:0:7} ko build github.com/linuxfoundation/lfx-v2-auth-service/cmd/server \ -B \ --platform linux/amd64,linux/arm64 \ diff --git a/.ko.yaml b/.ko.yaml new file mode 100644 index 0000000..d1756c3 --- /dev/null +++ b/.ko.yaml @@ -0,0 +1,9 @@ +# Copyright The Linux Foundation and each contributor to LFX. +# SPDX-License-Identifier: MIT +builds: + - id: server + dir: ./cmd/server + ldflags: + - -X=main.Version={{.Env.VERSION}} + - -X=main.BuildTime={{.Env.BUILD_TIME}} + - -X=main.GitCommit={{.Env.GIT_COMMIT}} From b4ae39d10837a8b0cd70cbe677ddddb93fcf0d85 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Tue, 3 Feb 2026 10:09:59 -0800 Subject: [PATCH 05/14] Add tests for pkg/log package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Test coverage for AppendCtx, Priority, PriorityCritical, contextHandler.Handle, and InitStructureLogConfig functions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- pkg/log/log_test.go | 343 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 343 insertions(+) create mode 100644 pkg/log/log_test.go diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go new file mode 100644 index 0000000..93f8744 --- /dev/null +++ b/pkg/log/log_test.go @@ -0,0 +1,343 @@ +// Copyright The Linux Foundation and each contributor to LFX. +// SPDX-License-Identifier: MIT + +package log + +import ( + "bytes" + "context" + "log/slog" + "testing" + "time" +) + +func TestAppendCtx(t *testing.T) { + tests := []struct { + name string + parentCtx context.Context + attr slog.Attr + existingAttrs []slog.Attr + expectedLen int + }{ + { + name: "nil parent context", + parentCtx: nil, + attr: slog.String("key", "value"), + expectedLen: 1, + }, + { + name: "empty context", + parentCtx: context.Background(), + attr: slog.String("key", "value"), + expectedLen: 1, + }, + { + name: "context with existing attrs", + parentCtx: context.WithValue(context.Background(), slogFields, []slog.Attr{slog.String("existing", "attr")}), + attr: slog.String("new", "attr"), + existingAttrs: []slog.Attr{slog.String("existing", "attr")}, + expectedLen: 2, + }, + { + name: "integer attribute", + parentCtx: context.Background(), + attr: slog.Int("count", 42), + expectedLen: 1, + }, + { + name: "bool attribute", + parentCtx: context.Background(), + attr: slog.Bool("enabled", true), + expectedLen: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := AppendCtx(tt.parentCtx, tt.attr) + + if result == nil { + t.Error("AppendCtx() returned nil context") + return + } + + attrs, ok := result.Value(slogFields).([]slog.Attr) + if !ok { + t.Error("AppendCtx() did not store attrs in context") + return + } + + if len(attrs) != tt.expectedLen { + t.Errorf("AppendCtx() stored %d attrs, want %d", len(attrs), tt.expectedLen) + } + + // Verify the new attr is in the slice + found := false + for _, a := range attrs { + if a.Key == tt.attr.Key && a.Value.String() == tt.attr.Value.String() { + found = true + break + } + } + if !found { + t.Errorf("AppendCtx() did not include the new attribute %v", tt.attr) + } + }) + } +} + +func TestAppendCtxMultipleAttrs(t *testing.T) { + t.Run("chaining multiple AppendCtx calls", func(t *testing.T) { + ctx := context.Background() + ctx = AppendCtx(ctx, slog.String("first", "1")) + ctx = AppendCtx(ctx, slog.String("second", "2")) + ctx = AppendCtx(ctx, slog.String("third", "3")) + + attrs, ok := ctx.Value(slogFields).([]slog.Attr) + if !ok { + t.Fatal("could not retrieve attrs from context") + } + + if len(attrs) != 3 { + t.Errorf("expected 3 attrs, got %d", len(attrs)) + } + + expectedKeys := []string{"first", "second", "third"} + for i, key := range expectedKeys { + if attrs[i].Key != key { + t.Errorf("attr[%d].Key = %q, want %q", i, attrs[i].Key, key) + } + } + }) +} + +func TestPriority(t *testing.T) { + tests := []struct { + name string + level string + expected string + }{ + { + name: "critical level", + level: "critical", + expected: "critical", + }, + { + name: "warning level", + level: "warning", + expected: "warning", + }, + { + name: "info level", + level: "info", + expected: "info", + }, + { + name: "empty level", + level: "", + expected: "", + }, + { + name: "custom level", + level: "custom-priority", + expected: "custom-priority", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Priority(tt.level) + + if result.Key != "priority" { + t.Errorf("Priority() key = %q, want %q", result.Key, "priority") + } + + if result.Value.String() != tt.expected { + t.Errorf("Priority(%q) value = %q, want %q", tt.level, result.Value.String(), tt.expected) + } + }) + } +} + +func TestPriorityCritical(t *testing.T) { + t.Run("returns critical priority attr", func(t *testing.T) { + result := PriorityCritical() + + if result.Key != "priority" { + t.Errorf("PriorityCritical() key = %q, want %q", result.Key, "priority") + } + + if result.Value.String() != priorityCritical { + t.Errorf("PriorityCritical() value = %q, want %q", result.Value.String(), priorityCritical) + } + }) +} + +// mockHandler is a test handler that captures records +type mockHandler struct { + records []slog.Record + attrs []slog.Attr +} + +func (m *mockHandler) Enabled(_ context.Context, _ slog.Level) bool { + return true +} + +func (m *mockHandler) Handle(_ context.Context, r slog.Record) error { + m.records = append(m.records, r) + r.Attrs(func(a slog.Attr) bool { + m.attrs = append(m.attrs, a) + return true + }) + return nil +} + +func (m *mockHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return m +} + +func (m *mockHandler) WithGroup(name string) slog.Handler { + return m +} + +func TestContextHandlerHandle(t *testing.T) { + tests := []struct { + name string + ctxAttrs []slog.Attr + recordMsg string + expectedAttrs int + }{ + { + name: "no context attrs", + ctxAttrs: nil, + recordMsg: "test message", + expectedAttrs: 0, + }, + { + name: "single context attr", + ctxAttrs: []slog.Attr{slog.String("key", "value")}, + recordMsg: "test message", + expectedAttrs: 1, + }, + { + name: "multiple context attrs", + ctxAttrs: []slog.Attr{ + slog.String("key1", "value1"), + slog.String("key2", "value2"), + slog.Int("key3", 123), + }, + recordMsg: "test message", + expectedAttrs: 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mock := &mockHandler{} + handler := contextHandler{Handler: mock} + + ctx := context.Background() + if tt.ctxAttrs != nil { + ctx = context.WithValue(ctx, slogFields, tt.ctxAttrs) + } + + record := slog.NewRecord(time.Now(), slog.LevelInfo, tt.recordMsg, 0) + err := handler.Handle(ctx, record) + + if err != nil { + t.Errorf("Handle() returned error: %v", err) + } + + if len(mock.attrs) != tt.expectedAttrs { + t.Errorf("Handle() captured %d attrs, want %d", len(mock.attrs), tt.expectedAttrs) + } + }) + } +} + +func TestContextHandlerHandleWithLogger(t *testing.T) { + t.Run("context attrs appear in log output", func(t *testing.T) { + var buf bytes.Buffer + baseHandler := slog.NewJSONHandler(&buf, nil) + handler := contextHandler{Handler: baseHandler} + logger := slog.New(handler) + + ctx := context.Background() + ctx = AppendCtx(ctx, slog.String("request_id", "test-123")) + ctx = AppendCtx(ctx, slog.String("user_id", "user-456")) + + logger.InfoContext(ctx, "test log message") + + output := buf.String() + if output == "" { + t.Error("no log output captured") + return + } + + // Verify context attrs are in the output + if !bytes.Contains(buf.Bytes(), []byte(`"request_id":"test-123"`)) { + t.Errorf("log output missing request_id: %s", output) + } + if !bytes.Contains(buf.Bytes(), []byte(`"user_id":"user-456"`)) { + t.Errorf("log output missing user_id: %s", output) + } + }) +} + +func TestInitStructureLogConfig(t *testing.T) { + tests := []struct { + name string + logLevel string + addSource string + wantLogLevel slog.Level + }{ + { + name: "debug level", + logLevel: "debug", + addSource: "false", + wantLogLevel: slog.LevelDebug, + }, + { + name: "info level", + logLevel: "info", + addSource: "false", + wantLogLevel: slog.LevelInfo, + }, + { + name: "warn level", + logLevel: "warn", + addSource: "false", + wantLogLevel: slog.LevelWarn, + }, + { + name: "default level on invalid", + logLevel: "invalid", + addSource: "false", + wantLogLevel: slog.LevelDebug, + }, + { + name: "default level on empty", + logLevel: "", + addSource: "false", + wantLogLevel: slog.LevelDebug, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set test env vars + t.Setenv("LOG_LEVEL", tt.logLevel) + t.Setenv("LOG_ADD_SOURCE", tt.addSource) + + // Call the function - it modifies global state + InitStructureLogConfig() + + // Verify the default logger is enabled at expected level + logger := slog.Default() + if !logger.Enabled(context.Background(), tt.wantLogLevel) { + t.Errorf("logger not enabled at level %v", tt.wantLogLevel) + } + }) + } +} + From 604b54b66af9a97889b2a9c8228a2b0e56111596 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Tue, 3 Feb 2026 10:16:25 -0800 Subject: [PATCH 06/14] Fix shellcheck warning in ko-build workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Declare and assign separately to avoid masking return values. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- .github/workflows/ko-build-branch.yaml | 6 ++++-- .github/workflows/ko-build-main.yaml | 6 ++++-- .github/workflows/ko-build-tag.yaml | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ko-build-branch.yaml b/.github/workflows/ko-build-branch.yaml index 7bf41e7..a745990 100644 --- a/.github/workflows/ko-build-branch.yaml +++ b/.github/workflows/ko-build-branch.yaml @@ -39,8 +39,10 @@ jobs: VERSION: ${{ steps.container_tag.outputs.tag }} GIT_COMMIT: ${{ github.sha }} run: | - export BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') - export GIT_COMMIT=${GIT_COMMIT:0:7} + BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export BUILD_TIME + GIT_COMMIT=${GIT_COMMIT:0:7} + export GIT_COMMIT ko build github.com/linuxfoundation/lfx-v2-auth-service/cmd/server \ -B \ --platform linux/amd64,linux/arm64 \ diff --git a/.github/workflows/ko-build-main.yaml b/.github/workflows/ko-build-main.yaml index 8bade5d..a76d370 100644 --- a/.github/workflows/ko-build-main.yaml +++ b/.github/workflows/ko-build-main.yaml @@ -32,8 +32,10 @@ jobs: VERSION: development GIT_COMMIT: ${{ github.sha }} run: | - export BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') - export GIT_COMMIT=${GIT_COMMIT:0:7} + BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export BUILD_TIME + GIT_COMMIT=${GIT_COMMIT:0:7} + export GIT_COMMIT ko build github.com/linuxfoundation/lfx-v2-auth-service/cmd/server \ -B \ --platform linux/amd64,linux/arm64 \ diff --git a/.github/workflows/ko-build-tag.yaml b/.github/workflows/ko-build-tag.yaml index 8229138..841aea8 100644 --- a/.github/workflows/ko-build-tag.yaml +++ b/.github/workflows/ko-build-tag.yaml @@ -58,8 +58,10 @@ jobs: VERSION: ${{ steps.prepare.outputs.app_version }} GIT_COMMIT: ${{ github.sha }} run: | - export BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') - export GIT_COMMIT=${GIT_COMMIT:0:7} + BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') + export BUILD_TIME + GIT_COMMIT=${GIT_COMMIT:0:7} + export GIT_COMMIT ko build github.com/linuxfoundation/lfx-v2-auth-service/cmd/server \ -B \ --platform linux/amd64,linux/arm64 \ From fbcb1106e9b98bee374493e9e404c20481d4d6eb Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Tue, 3 Feb 2026 10:24:05 -0800 Subject: [PATCH 07/14] Remove trailing backslash from ko build command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- .github/workflows/ko-build-branch.yaml | 2 +- .github/workflows/ko-build-main.yaml | 2 +- .github/workflows/ko-build-tag.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ko-build-branch.yaml b/.github/workflows/ko-build-branch.yaml index a745990..7004895 100644 --- a/.github/workflows/ko-build-branch.yaml +++ b/.github/workflows/ko-build-branch.yaml @@ -48,4 +48,4 @@ jobs: --platform linux/amd64,linux/arm64 \ -t ${{ github.sha }} \ -t ${{ steps.container_tag.outputs.tag }} \ - --sbom spdx \ + --sbom spdx diff --git a/.github/workflows/ko-build-main.yaml b/.github/workflows/ko-build-main.yaml index a76d370..5a3ca8f 100644 --- a/.github/workflows/ko-build-main.yaml +++ b/.github/workflows/ko-build-main.yaml @@ -41,4 +41,4 @@ jobs: --platform linux/amd64,linux/arm64 \ -t ${{ github.sha }} \ -t development \ - --sbom spdx \ + --sbom spdx diff --git a/.github/workflows/ko-build-tag.yaml b/.github/workflows/ko-build-tag.yaml index 841aea8..663f56f 100644 --- a/.github/workflows/ko-build-tag.yaml +++ b/.github/workflows/ko-build-tag.yaml @@ -68,7 +68,7 @@ jobs: -t ${{ github.ref_name }} \ -t ${{ steps.prepare.outputs.app_version }} \ -t latest \ - --sbom spdx \ + --sbom spdx release-helm-chart: needs: publish From 743e3c1e036dce9d045726078b72a3f102b3555a Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Tue, 3 Feb 2026 14:24:27 -0800 Subject: [PATCH 08/14] Remove redundant logging package, fix log context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete pkg/logging/context.go (slog-otel handles this) - Use WarnContext in authelia/user.go for trace correlation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- internal/infrastructure/authelia/user.go | 2 +- pkg/logging/context.go | 37 ------------------------ 2 files changed, 1 insertion(+), 38 deletions(-) delete mode 100644 pkg/logging/context.go diff --git a/internal/infrastructure/authelia/user.go b/internal/infrastructure/authelia/user.go index de40b36..2e67417 100644 --- a/internal/infrastructure/authelia/user.go +++ b/internal/infrastructure/authelia/user.go @@ -479,7 +479,7 @@ func NewUserReaderWriter(ctx context.Context, config map[string]string, natsClie errSyncUsers := u.sync.syncUsers(ctx, u.storage, u.orchestrator) if errSyncUsers != nil { - slog.Warn("failed to sync from storage to orchestrator", "error", errSyncUsers) + slog.WarnContext(ctx, "failed to sync from storage to orchestrator", "error", errSyncUsers) } return u, nil diff --git a/pkg/logging/context.go b/pkg/logging/context.go deleted file mode 100644 index c1c604b..0000000 --- a/pkg/logging/context.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright The Linux Foundation and each contributor to LFX. -// SPDX-License-Identifier: MIT - -package logging - -import ( - "context" - "log/slog" - - "go.opentelemetry.io/otel/trace" -) - -// LogAttrsFromContext extracts trace_id and span_id from the context and returns them as slog attributes. -// Use this to add tracing context to log messages for correlation. -func LogAttrsFromContext(ctx context.Context) []slog.Attr { - spanCtx := trace.SpanContextFromContext(ctx) - if !spanCtx.IsValid() { - return nil - } - return []slog.Attr{ - slog.String("trace_id", spanCtx.TraceID().String()), - slog.String("span_id", spanCtx.SpanID().String()), - } -} - -// LogWithContext returns a logger with trace context attributes added. -// Usage: logging.LogWithContext(ctx, slog.Default()).Info("message", "key", "value") -func LogWithContext(ctx context.Context, logger *slog.Logger) *slog.Logger { - spanCtx := trace.SpanContextFromContext(ctx) - if !spanCtx.IsValid() { - return logger - } - return logger.With( - slog.String("trace_id", spanCtx.TraceID().String()), - slog.String("span_id", spanCtx.SpanID().String()), - ) -} From d017e3bfeb1a742be26145744af86789be8fb233 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Tue, 3 Feb 2026 14:30:53 -0800 Subject: [PATCH 09/14] Add OTEL tracing to outgoing HTTP requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wrap HTTP client transport with otelhttp.NewTransport to enable distributed tracing for outgoing API calls. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- pkg/httpclient/client.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/httpclient/client.go b/pkg/httpclient/client.go index 1b878dc..12bc2aa 100644 --- a/pkg/httpclient/client.go +++ b/pkg/httpclient/client.go @@ -11,6 +11,8 @@ import ( "net/http" "strings" "time" + + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" ) // Client represents a generic HTTP client with retry logic @@ -155,12 +157,14 @@ func (c *Client) Request(ctx context.Context, verb, url string, body io.Reader, return c.Do(ctx, req) } -// NewClient creates a new HTTP client with the given configuration +// NewClient creates a new HTTP client with the given configuration. +// The client is instrumented with OpenTelemetry for distributed tracing. func NewClient(config Config) *Client { return &Client{ config: config, httpClient: &http.Client{ - Timeout: config.Timeout, + Timeout: config.Timeout, + Transport: otelhttp.NewTransport(http.DefaultTransport), }, } } From 7127f783cb4c000b8b8b1482913f98efe71e5955 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Thu, 5 Feb 2026 13:07:50 -0800 Subject: [PATCH 10/14] Fix go.mod tidy, use idiomatic nil slices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run go mod tidy to move otel/trace to indirect. Replace empty slice literals with var declarations and trim propagator string before switch statement. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- go.mod | 2 +- pkg/log/log_test.go | 1 - pkg/utils/otel.go | 15 ++++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 26a172c..ea72a3c 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,6 @@ require ( go.opentelemetry.io/otel/sdk v1.39.0 go.opentelemetry.io/otel/sdk/log v0.15.0 go.opentelemetry.io/otel/sdk/metric v1.39.0 - go.opentelemetry.io/otel/trace v1.39.0 go.yaml.in/yaml/v2 v2.4.2 goa.design/clue v1.2.3 goa.design/goa/v3 v3.23.3 @@ -87,6 +86,7 @@ require ( go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 // indirect go.opentelemetry.io/otel/metric v1.39.0 // indirect go.opentelemetry.io/otel/sdk/log/logtest v0.15.0 // indirect + go.opentelemetry.io/otel/trace v1.39.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.31.0 // indirect diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go index 93f8744..0d4c7b2 100644 --- a/pkg/log/log_test.go +++ b/pkg/log/log_test.go @@ -340,4 +340,3 @@ func TestInitStructureLogConfig(t *testing.T) { }) } } - diff --git a/pkg/utils/otel.go b/pkg/utils/otel.go index 91e0fb3..f92f8f2 100644 --- a/pkg/utils/otel.go +++ b/pkg/utils/otel.go @@ -256,7 +256,8 @@ func newPropagator(cfg OTelConfig) propagation.TextMapPropagator { var propagators []propagation.TextMapPropagator for _, p := range strings.Split(cfg.Propagators, ",") { - switch strings.TrimSpace(p) { + p = strings.TrimSpace(p) + switch p { case "tracecontext": propagators = append(propagators, propagation.TraceContext{}) case "baggage": @@ -286,7 +287,7 @@ func newTraceProvider(ctx context.Context, cfg OTelConfig, res *resource.Resourc var err error if cfg.Protocol == OTelProtocolHTTP { - opts := []otlptracehttp.Option{} + var opts []otlptracehttp.Option if cfg.Endpoint != "" { opts = append(opts, otlptracehttp.WithEndpoint(cfg.Endpoint)) } @@ -295,7 +296,7 @@ func newTraceProvider(ctx context.Context, cfg OTelConfig, res *resource.Resourc } exporter, err = otlptracehttp.New(ctx, opts...) } else { - opts := []otlptracegrpc.Option{} + var opts []otlptracegrpc.Option if cfg.Endpoint != "" { opts = append(opts, otlptracegrpc.WithEndpoint(cfg.Endpoint)) } @@ -325,7 +326,7 @@ func newMetricsProvider(ctx context.Context, cfg OTelConfig, res *resource.Resou var err error if cfg.Protocol == OTelProtocolHTTP { - opts := []otlpmetrichttp.Option{} + var opts []otlpmetrichttp.Option if cfg.Endpoint != "" { opts = append(opts, otlpmetrichttp.WithEndpoint(cfg.Endpoint)) } @@ -334,7 +335,7 @@ func newMetricsProvider(ctx context.Context, cfg OTelConfig, res *resource.Resou } exporter, err = otlpmetrichttp.New(ctx, opts...) } else { - opts := []otlpmetricgrpc.Option{} + var opts []otlpmetricgrpc.Option if cfg.Endpoint != "" { opts = append(opts, otlpmetricgrpc.WithEndpoint(cfg.Endpoint)) } @@ -363,7 +364,7 @@ func newLoggerProvider(ctx context.Context, cfg OTelConfig, res *resource.Resour var err error if cfg.Protocol == OTelProtocolHTTP { - opts := []otlploghttp.Option{} + var opts []otlploghttp.Option if cfg.Endpoint != "" { opts = append(opts, otlploghttp.WithEndpoint(cfg.Endpoint)) } @@ -372,7 +373,7 @@ func newLoggerProvider(ctx context.Context, cfg OTelConfig, res *resource.Resour } exporter, err = otlploghttp.New(ctx, opts...) } else { - opts := []otlploggrpc.Option{} + var opts []otlploggrpc.Option if cfg.Endpoint != "" { opts = append(opts, otlploggrpc.WithEndpoint(cfg.Endpoint)) } From b6a205f8e6fc8b82513262bb982dc046c02d1698 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Thu, 5 Feb 2026 14:30:58 -0800 Subject: [PATCH 11/14] Upgrade otel libraries to v1.40.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.6 Signed-off-by: Trevor Bramwell --- go.mod | 33 ++++++++++++++-------------- go.sum | 68 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/go.mod b/go.mod index ea72a3c..6532f51 100644 --- a/go.mod +++ b/go.mod @@ -15,17 +15,17 @@ require ( github.com/stretchr/testify v1.11.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 - go.opentelemetry.io/otel v1.39.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 - go.opentelemetry.io/otel/log v0.15.0 - go.opentelemetry.io/otel/sdk v1.39.0 - go.opentelemetry.io/otel/sdk/log v0.15.0 - go.opentelemetry.io/otel/sdk/metric v1.39.0 + go.opentelemetry.io/otel v1.40.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 + go.opentelemetry.io/otel/log v0.16.0 + go.opentelemetry.io/otel/sdk v1.40.0 + go.opentelemetry.io/otel/sdk/log v0.16.0 + go.opentelemetry.io/otel/sdk/metric v1.40.0 go.yaml.in/yaml/v2 v2.4.2 goa.design/clue v1.2.3 goa.design/goa/v3 v3.23.3 @@ -59,7 +59,7 @@ require ( github.com/gohugoio/hashstructure v0.6.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect @@ -82,11 +82,10 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.devnw.com/structs v1.0.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/sdk/log/logtest v0.15.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.31.0 // indirect diff --git a/go.sum b/go.sum index 93f20e5..895ceff 100644 --- a/go.sum +++ b/go.sum @@ -66,8 +66,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6 h1:1ufTZkFXIQQ9EmgPjcIPIi2krfxG03lQ8OLoY1MJ3UM= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.6/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -159,38 +159,38 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGN go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ= go.opentelemetry.io/contrib/propagators/jaeger v1.39.0 h1:Gz3yKzfMSEFzF0Vy5eIpu9ndpo4DhXMCxsLMF0OOApo= go.opentelemetry.io/contrib/propagators/jaeger v1.39.0/go.mod h1:2D/cxxCqTlrday0rZrPujjg5aoAdqk1NaNyoXn8FJn8= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v3pa5PgVf2xoFMi5YtNR06WtS7ve5pcvLtM= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0/go.mod h1:JM31r0GGZ/GU94mX8hN4D8v6e40aFlUECSQ48HaLgHM= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0 h1:EKpiGphOYq3CYnIe2eX9ftUkyU+Y8Dtte8OaWyHJ4+I= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.15.0/go.mod h1:nWFP7C+T8TygkTjJ7mAyEaFaE7wNfms3nV/vexZ6qt0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0 h1:nKP4Z2ejtHn3yShBb+2KawiXgpn8In5cT7aO2wXuOTE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.39.0/go.mod h1:NwjeBbNigsO4Aj9WgM0C+cKIrxsZUaRmZUO7A8I7u8o= -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/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c= -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/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g= -go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY= -go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE= -go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ= -go.opentelemetry.io/otel/sdk/log/logtest v0.15.0 h1:O+dZyt9riqVDKZwFRFn9zVdUKam3uwLMud+poHRssd4= -go.opentelemetry.io/otel/sdk/log/logtest v0.15.0/go.mod h1:j7aD3tWSt3KlzWz5G+9loktEng6udEY868Kc2XDzdII= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.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/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= +go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= +go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= +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/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= +go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0 h1:/XVkpZ41rVRTP4DfMgYv1nEtNmf65XPPyAdqV90TMy4= +go.opentelemetry.io/otel/sdk/log/logtest v0.16.0/go.mod h1:iOOPgQr5MY9oac/F5W86mXdeyWZGleIx3uXO98X2R6Y= +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/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= From 8918f459b85c6f4a5e60a564a5f093d16a6e27b3 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Sat, 7 Feb 2026 11:16:35 -0800 Subject: [PATCH 12/14] Update semconv to v1.39.0 for otel v1.40.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The otel SDK v1.40.0 uses schema v1.39.0 internally. Using semconv v1.37.0 caused a conflicting Schema URL error in tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- pkg/utils/otel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/utils/otel.go b/pkg/utils/otel.go index f92f8f2..4a08b9c 100644 --- a/pkg/utils/otel.go +++ b/pkg/utils/otel.go @@ -26,7 +26,7 @@ import ( "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.37.0" + semconv "go.opentelemetry.io/otel/semconv/v1.39.0" ) const ( From 4bebd1bd4fe63de381aa3ff604042670e43dd3c2 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Sat, 7 Feb 2026 11:20:15 -0800 Subject: [PATCH 13/14] Fix serviceVersion default in chart docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The version is set at build-time via ldflags, not hardcoded to "1.0.0". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- charts/lfx-v2-auth-service/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/lfx-v2-auth-service/values.yaml b/charts/lfx-v2-auth-service/values.yaml index e730a75..46cc398 100644 --- a/charts/lfx-v2-auth-service/values.yaml +++ b/charts/lfx-v2-auth-service/values.yaml @@ -148,7 +148,7 @@ app: serviceName: "" # serviceVersion is the service version for OpenTelemetry resource # identification - # (default: "1.0.0") + # (default: build-time version from ldflags) serviceVersion: "" # protocol specifies the OTLP protocol: "grpc" or "http" # (default: "grpc") From feb7343d73333f5e34a9ae771b74fb7855d5e8c1 Mon Sep 17 00:00:00 2001 From: Trevor Bramwell Date: Sat, 7 Feb 2026 11:22:28 -0800 Subject: [PATCH 14/14] Fix global slog state leak in log tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Save and restore slog.Default() in each subtest of TestInitStructureLogConfig to prevent InitStructureLogConfig from leaking global logger state into other tests. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Issue: LFXV2-608 Co-Authored-By: Claude Opus 4.5 Signed-off-by: Trevor Bramwell --- pkg/log/log_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go index 0d4c7b2..fb816ff 100644 --- a/pkg/log/log_test.go +++ b/pkg/log/log_test.go @@ -325,6 +325,9 @@ func TestInitStructureLogConfig(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + prev := slog.Default() + defer slog.SetDefault(prev) + // Set test env vars t.Setenv("LOG_LEVEL", tt.logLevel) t.Setenv("LOG_ADD_SOURCE", tt.addSource)