diff --git a/.golangci.yaml b/.golangci.yaml index d886a4fb1e..59bf0ad535 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -22,7 +22,13 @@ linters-settings: depguard: rules: everything: + list-mode: lax + allow: + - go.opentelemetry.io/otel/semconv/v1.27.0 deny: + - pkg: go.opentelemetry.io/otel/semconv + desc: Use "go.opentelemetry.io/otel/semconv/v1.27.0" instead. + - pkg: io/ioutil desc: > Use the "io" and "os" packages instead. @@ -93,6 +99,13 @@ linters-settings: alias: apierrors no-unaliased: true + spancheck: + checks: [end, record-error] + extra-start-span-signatures: + - 'github.com/crunchydata/postgres-operator/internal/tracing.Start:opentelemetry' + ignore-check-signatures: + - 'tracing.Escape' + issues: exclude-generated: strict exclude-rules: diff --git a/cmd/postgres-operator/main.go b/cmd/postgres-operator/main.go index 143e420597..908a04bb74 100644 --- a/cmd/postgres-operator/main.go +++ b/cmd/postgres-operator/main.go @@ -6,15 +6,17 @@ package main import ( "context" + "errors" "fmt" "net/http" "os" + "os/signal" "strconv" "strings" + "syscall" "time" "unicode" - "go.opentelemetry.io/otel" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/healthz" @@ -31,12 +33,11 @@ import ( "github.com/crunchydata/postgres-operator/internal/logging" "github.com/crunchydata/postgres-operator/internal/naming" "github.com/crunchydata/postgres-operator/internal/registration" + "github.com/crunchydata/postgres-operator/internal/tracing" "github.com/crunchydata/postgres-operator/internal/upgradecheck" "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) -var versionString string - // assertNoError panics when err is not nil. func assertNoError(err error) { if err != nil { @@ -58,8 +59,8 @@ func initLogging() { //+kubebuilder:rbac:groups="coordination.k8s.io",resources="leases",verbs={get,create,update,watch} -func initManager() (runtime.Options, error) { - log := logging.FromContext(context.Background()) +func initManager(ctx context.Context) (runtime.Options, error) { + log := logging.FromContext(ctx) options := runtime.Options{} options.Cache.SyncPeriod = initialize.Pointer(time.Hour) @@ -120,33 +121,55 @@ func initManager() (runtime.Options, error) { } func main() { - // This context is canceled by SIGINT, SIGTERM, or by calling shutdown. - ctx, shutdown := context.WithCancel(runtime.SignalHandler()) - - otelFlush, err := initOpenTelemetry() - assertNoError(err) - defer otelFlush() + running, stopRunning := context.WithCancel(context.Background()) + defer stopRunning() + initVersion() initLogging() - - log := logging.FromContext(ctx) + log := logging.FromContext(running) log.V(1).Info("debug flag set to true") + // Start a goroutine that waits for SIGINT or SIGTERM. + { + signals := []os.Signal{os.Interrupt, syscall.SIGTERM} + receive := make(chan os.Signal, len(signals)) + signal.Notify(receive, signals...) + go func() { + // Wait for a signal then immediately restore the default signal handlers. + // After this, a SIGHUP, SIGINT, or SIGTERM causes the program to exit. + // - https://pkg.go.dev/os/signal#hdr-Default_behavior_of_signals_in_Go_programs + s := <-receive + signal.Stop(receive) + + log.Info("received signal from OS", "signal", s.String()) + stopRunning() + }() + } + features := feature.NewGate() assertNoError(features.Set(os.Getenv("PGO_FEATURE_GATES"))) - ctx = feature.NewContext(ctx, features) + running = feature.NewContext(running, features) log.Info("feature gates", // These are set by the user - "PGO_FEATURE_GATES", feature.ShowAssigned(ctx), + "PGO_FEATURE_GATES", feature.ShowAssigned(running), // These are enabled, including features that are on by default - "enabled", feature.ShowEnabled(ctx)) + "enabled", feature.ShowEnabled(running)) + + // Initialize OpenTelemetry and flush data when there is a panic. + otelFinish, err := initOpenTelemetry(running) + assertNoError(err) + defer func(ctx context.Context) { _ = otelFinish(ctx) }(running) + + tracing.SetDefaultTracer(tracing.New("github.com/CrunchyData/postgres-operator")) cfg, err := runtime.GetConfig() assertNoError(err) + cfg.UserAgent = userAgent cfg.Wrap(otelTransportWrapper()) + // TODO(controller-runtime): Set config.WarningHandler instead after v0.19.0. // Configure client-go to suppress warnings when warning headers are encountered. This prevents // warnings from being logged over and over again during reconciliation (e.g. this will suppress // deprecation warnings when using an older version of a resource for backwards compatibility). @@ -154,11 +177,11 @@ func main() { k8s, err := kubernetes.NewDiscoveryRunner(cfg) assertNoError(err) - assertNoError(k8s.Read(ctx)) + assertNoError(k8s.Read(running)) - log.Info("Connected to Kubernetes", "api", k8s.Version().String(), "openshift", k8s.IsOpenShift()) + log.Info("connected to Kubernetes", "api", k8s.Version().String(), "openshift", k8s.IsOpenShift()) - options, err := initManager() + options, err := initManager(running) assertNoError(err) // Add to the Context that Manager passes to Reconciler.Start, Runnable.Start, @@ -174,7 +197,7 @@ func main() { assertNoError(err) assertNoError(mgr.Add(k8s)) - registrar, err := registration.NewRunner(os.Getenv("RSA_KEY"), os.Getenv("TOKEN_PATH"), shutdown) + registrar, err := registration.NewRunner(os.Getenv("RSA_KEY"), os.Getenv("TOKEN_PATH"), stopRunning) assertNoError(err) assertNoError(mgr.Add(registrar)) token, _ := registrar.CheckToken() @@ -212,10 +235,29 @@ func main() { assertNoError(mgr.AddHealthzCheck("health", healthz.Ping)) assertNoError(mgr.AddReadyzCheck("check", healthz.Ping)) - log.Info("starting controller runtime manager and will wait for signal to exit") + // Start the manager and wait for its context to be canceled. + stopped := make(chan error, 1) + go func() { stopped <- mgr.Start(running) }() + <-running.Done() + + // Set a deadline for graceful termination. + log.Info("shutting down") + stopping, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + + // Wait for the manager to return or the deadline to pass. + select { + case err = <-stopped: + case <-stopping.Done(): + err = stopping.Err() + } - assertNoError(mgr.Start(ctx)) - log.Info("signal received, exiting") + // Flush any telemetry with the remaining time we have. + if err = errors.Join(err, otelFinish(stopping)); err != nil { + log.Error(err, "shutdown failed") + } else { + log.Info("shutdown complete") + } } // addControllersToManager adds all PostgreSQL Operator controllers to the provided controller @@ -226,7 +268,6 @@ func addControllersToManager(mgr runtime.Manager, log logging.Logger, reg regist Owner: postgrescluster.ControllerName, Recorder: mgr.GetEventRecorderFor(postgrescluster.ControllerName), Registration: reg, - Tracer: otel.Tracer(postgrescluster.ControllerName), } if err := pgReconciler.SetupWithManager(mgr); err != nil { diff --git a/cmd/postgres-operator/main_test.go b/cmd/postgres-operator/main_test.go index f369ce6bd3..a36cd21a13 100644 --- a/cmd/postgres-operator/main_test.go +++ b/cmd/postgres-operator/main_test.go @@ -5,6 +5,7 @@ package main import ( + "context" "reflect" "testing" "time" @@ -14,8 +15,10 @@ import ( ) func TestInitManager(t *testing.T) { + ctx := context.Background() + t.Run("Defaults", func(t *testing.T) { - options, err := initManager() + options, err := initManager(ctx) assert.NilError(t, err) if assert.Check(t, options.Cache.SyncPeriod != nil) { @@ -48,7 +51,7 @@ func TestInitManager(t *testing.T) { t.Run("Invalid", func(t *testing.T) { t.Setenv("PGO_CONTROLLER_LEASE_NAME", "INVALID_NAME") - options, err := initManager() + options, err := initManager(ctx) assert.ErrorContains(t, err, "PGO_CONTROLLER_LEASE_NAME") assert.ErrorContains(t, err, "invalid") @@ -59,7 +62,7 @@ func TestInitManager(t *testing.T) { t.Run("Valid", func(t *testing.T) { t.Setenv("PGO_CONTROLLER_LEASE_NAME", "valid-name") - options, err := initManager() + options, err := initManager(ctx) assert.NilError(t, err) assert.Assert(t, options.LeaderElection == true) assert.Equal(t, options.LeaderElectionNamespace, "test-namespace") @@ -70,7 +73,7 @@ func TestInitManager(t *testing.T) { t.Run("PGO_TARGET_NAMESPACE", func(t *testing.T) { t.Setenv("PGO_TARGET_NAMESPACE", "some-such") - options, err := initManager() + options, err := initManager(ctx) assert.NilError(t, err) assert.Assert(t, cmp.Len(options.Cache.DefaultNamespaces, 1), "expected only one configured namespace") @@ -81,7 +84,7 @@ func TestInitManager(t *testing.T) { t.Run("PGO_TARGET_NAMESPACES", func(t *testing.T) { t.Setenv("PGO_TARGET_NAMESPACES", "some-such,another-one") - options, err := initManager() + options, err := initManager(ctx) assert.NilError(t, err) assert.Assert(t, cmp.Len(options.Cache.DefaultNamespaces, 2), "expect two configured namespaces") @@ -95,7 +98,7 @@ func TestInitManager(t *testing.T) { for _, v := range []string{"-3", "0", "3.14"} { t.Setenv("PGO_WORKERS", v) - options, err := initManager() + options, err := initManager(ctx) assert.NilError(t, err) assert.DeepEqual(t, options.Controller.GroupKindConcurrency, map[string]int{ @@ -107,7 +110,7 @@ func TestInitManager(t *testing.T) { t.Run("Valid", func(t *testing.T) { t.Setenv("PGO_WORKERS", "19") - options, err := initManager() + options, err := initManager(ctx) assert.NilError(t, err) assert.DeepEqual(t, options.Controller.GroupKindConcurrency, map[string]int{ diff --git a/cmd/postgres-operator/open_telemetry.go b/cmd/postgres-operator/open_telemetry.go index 2c9eedc135..02b12b19fa 100644 --- a/cmd/postgres-operator/open_telemetry.go +++ b/cmd/postgres-operator/open_telemetry.go @@ -6,79 +6,91 @@ package main import ( "context" - "fmt" - "io" + "errors" "net/http" "os" + "go.opentelemetry.io/contrib/exporters/autoexport" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/contrib/propagators/autoprop" "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" - "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" + "go.opentelemetry.io/otel/sdk/resource" "go.opentelemetry.io/otel/sdk/trace" -) + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" -func initOpenTelemetry() (func(), error) { - // At the time of this writing, the SDK (go.opentelemetry.io/otel@v1.2.0) - // does not automatically initialize any exporter. We import the OTLP and - // stdout exporters and configure them below. Much of the OTLP exporter can - // be configured through environment variables. - // - // - https://github.com/open-telemetry/opentelemetry-go/issues/2310 - // - https://github.com/open-telemetry/opentelemetry-specification/blob/v1.8.0/specification/sdk-environment-variables.md + "github.com/crunchydata/postgres-operator/internal/logging" +) - switch os.Getenv("OTEL_TRACES_EXPORTER") { - case "json": - var closer io.Closer - filename := os.Getenv("OTEL_JSON_FILE") - options := []stdouttrace.Option{} +func initOpenTelemetry(ctx context.Context) (func(context.Context) error, error) { + var started []interface{ Shutdown(context.Context) error } - if filename != "" { - file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return nil, fmt.Errorf("unable to open exporter file: %w", err) - } - closer = file - options = append(options, stdouttrace.WithWriter(file)) + // shutdown returns the results of calling all the Shutdown methods in started. + var shutdown = func(ctx context.Context) error { + var err error + for _, s := range started { + err = errors.Join(err, s.Shutdown(ctx)) } + started = nil + return err + } - exporter, err := stdouttrace.New(options...) - if err != nil { - return nil, fmt.Errorf("unable to initialize stdout exporter: %w", err) - } + // The default for OTEL_PROPAGATORS is "tracecontext,baggage". + otel.SetTextMapPropagator(autoprop.NewTextMapPropagator()) - provider := trace.NewTracerProvider(trace.WithBatcher(exporter)) - flush := func() { - _ = provider.Shutdown(context.TODO()) - if closer != nil { - _ = closer.Close() - } - } + // Skip any remaining setup when OTEL_SDK_DISABLED is exactly "true". + // - https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables + if os.Getenv("OTEL_SDK_DISABLED") == "true" { + return shutdown, nil + } - otel.SetTracerProvider(provider) - return flush, nil + log := logging.FromContext(ctx).WithName("open-telemetry") + otel.SetLogger(log) + otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) { + // TODO(events): Emit this as an event instead. + log.V(1).Info(semconv.ExceptionEventName, + string(semconv.ExceptionMessageKey), err) + })) - case "otlp": - client := otlptracehttp.NewClient() - exporter, err := otlptrace.New(context.TODO(), client) - if err != nil { - return nil, fmt.Errorf("unable to initialize OTLP exporter: %w", err) - } + // Build a resource from the OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables. + // - https://opentelemetry.io/docs/languages/go/resources + self, _ := resource.Merge(resource.NewSchemaless( + semconv.ServiceVersion(versionString), + ), resource.Default()) - provider := trace.NewTracerProvider(trace.WithBatcher(exporter)) - flush := func() { - _ = provider.Shutdown(context.TODO()) + // Provide defaults for some other detectable attributes. + if r, err := resource.New(ctx, + resource.WithProcessRuntimeName(), + resource.WithProcessRuntimeVersion(), + resource.WithProcessRuntimeDescription(), + ); err == nil { + self, _ = resource.Merge(r, self) + } + if r, err := resource.New(ctx, + resource.WithHost(), + resource.WithOS(), + ); err == nil { + self, _ = resource.Merge(r, self) + } + + // The default for OTEL_TRACES_EXPORTER is "otlp" but we prefer "none". + // Only assign an exporter when the environment variable is set. + if os.Getenv("OTEL_TRACES_EXPORTER") != "" { + exporter, err := autoexport.NewSpanExporter(ctx) + if err != nil { + return nil, errors.Join(err, shutdown(ctx)) } + // The defaults for this batch processor come from the OTEL_BSP_* environment variables. + // - https://pkg.go.dev/go.opentelemetry.io/otel/sdk/internal/env + provider := trace.NewTracerProvider( + trace.WithBatcher(exporter), + trace.WithResource(self), + ) + started = append(started, provider) otel.SetTracerProvider(provider) - return flush, nil } - // $OTEL_TRACES_EXPORTER is unset or unknown, so no TracerProvider has been assigned. - // The default at this time is a single "no-op" tracer. - - return func() {}, nil + return shutdown, nil } // otelTransportWrapper creates a function that wraps the provided net/http.RoundTripper diff --git a/cmd/postgres-operator/version.go b/cmd/postgres-operator/version.go new file mode 100644 index 0000000000..0b04ce95e8 --- /dev/null +++ b/cmd/postgres-operator/version.go @@ -0,0 +1,26 @@ +// Copyright 2017 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "os" + "path/filepath" + "runtime" +) + +var userAgent string +var versionString string + +func initVersion() { + command := "unknown" + if len(os.Args) > 0 && len(os.Args[0]) > 0 { + command = filepath.Base(os.Args[0]) + } + if len(versionString) > 0 { + command += "/" + versionString + } + userAgent = fmt.Sprintf("%s (%s/%s)", command, runtime.GOOS, runtime.GOARCH) +} diff --git a/go.mod b/go.mod index df4430df70..26856e4456 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/crunchydata/postgres-operator -go 1.22.0 +go 1.22.7 require ( github.com/go-logr/logr v1.4.2 @@ -14,14 +14,13 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.3 github.com/xdg-go/stringprep v1.0.2 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 - go.opentelemetry.io/otel v1.28.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.2.0 - go.opentelemetry.io/otel/sdk v1.28.0 - go.opentelemetry.io/otel/trace v1.28.0 - golang.org/x/crypto v0.27.0 + go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 + go.opentelemetry.io/contrib/propagators/autoprop v0.57.0 + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/sdk v1.32.0 + go.opentelemetry.io/otel/trace v1.32.0 + golang.org/x/crypto v0.28.0 golang.org/x/tools v0.22.0 gotest.tools/v3 v3.1.0 k8s.io/api v0.31.0 @@ -54,38 +53,59 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/spdystream v0.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 // indirect + go.opentelemetry.io/contrib/propagators/aws v1.32.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.32.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.32.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 // indirect + go.opentelemetry.io/otel/log v0.8.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk/log v0.8.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 // indirect golang.org/x/mod v0.18.0 // indirect - golang.org/x/net v0.29.0 // indirect - golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/grpc v1.66.2 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/grpc v1.68.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 25698530d8..86b776257e 100644 --- a/go.sum +++ b/go.sum @@ -49,7 +49,6 @@ github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6 github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -62,8 +61,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.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -72,12 +71,16 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kubernetes-csi/external-snapshotter/client/v8 v8.0.0 h1:mjQG0Vakr2h246kEDR85U8y8ZhPgT3bguTCajRa/jaw= github.com/kubernetes-csi/external-snapshotter/client/v8 v8.0.0/go.mod h1:E3vdYxHj2C2q6qo8/Da4g7P+IcwqRZyy3gJBzYybV9Y= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= @@ -102,16 +105,16 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -128,25 +131,58 @@ github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyh github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.2.0/go.mod h1:aT17Fk0Z1Nor9e0uisf98LrntPGMnk4frBO9+dkf69I= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0 h1:QY7/0NeRPKlzusf40ZE4t1VlMKbqSNT7cJRYzWuja0s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.27.0/go.mod h1:HVkSiDhTM9BoUJU8qE6j2eSWLLXvi1USXjyd2BXT8PY= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.2.0 h1:OiYdrCq1Ctwnovp6EofSPwlp5aGy4LgKNbkg7PtEUw8= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.2.0/go.mod h1:DUFCmFkXr0VtAHl5Zq2JRx24G6ze5CAq8YfdD36RdX8= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.2.0/go.mod h1:jNN8QtpvbsKhgaC6V5lHiejMoKD+V8uadoSafgHPx1U= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.2.0/go.mod h1:N5FLswTubnxKxOJHM7XZC074qpeEdLy3CgAVsdMucK0= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= +go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= +go.opentelemetry.io/contrib/exporters/autoexport v0.57.0/go.mod h1:EJBheUMttD/lABFyLXhce47Wr6DPWYReCzaZiXadH7g= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94= +go.opentelemetry.io/contrib/propagators/autoprop v0.57.0 h1:bNPJOdT5154XxzeFmrh8R+PXnV4t3TZEczy8gHEpcpg= +go.opentelemetry.io/contrib/propagators/autoprop v0.57.0/go.mod h1:Tb0j0mK+QatKdCxCKPN7CSzc7kx/q34/KaohJx/N96s= +go.opentelemetry.io/contrib/propagators/aws v1.32.0 h1:NELzr8bW7a7aHVZj5gaep1PfkvoSCGx+1qNGZx/uhhU= +go.opentelemetry.io/contrib/propagators/aws v1.32.0/go.mod h1:XKMrzHNka3eOA+nGEcNKYVL9s77TAhkwQEynYuaRFnQ= +go.opentelemetry.io/contrib/propagators/b3 v1.32.0 h1:MazJBz2Zf6HTN/nK/s3Ru1qme+VhWU5hm83QxEP+dvw= +go.opentelemetry.io/contrib/propagators/b3 v1.32.0/go.mod h1:B0s70QHYPrJwPOwD1o3V/R8vETNOG9N3qZf4LDYvA30= +go.opentelemetry.io/contrib/propagators/jaeger v1.32.0 h1:K/fOyTMD6GELKTIJBaJ9k3ppF2Njt8MeUGBOwfaWXXA= +go.opentelemetry.io/contrib/propagators/jaeger v1.32.0/go.mod h1:ISE6hda//MTWvtngG7p4et3OCngsrTVfl7c6DjN17f8= +go.opentelemetry.io/contrib/propagators/ot v1.32.0 h1:Poy02A4wOZubHyd2hpHPDgZW+rn6EIq0vCwTZJ6Lmu8= +go.opentelemetry.io/contrib/propagators/ot v1.32.0/go.mod h1:cbhaURV+VR3NIMarzDYZU1RDEkXG1fNd1WMP1XCcGkY= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0 h1:CHXNXwfKWfzS65yrlB2PVds1IBZcdsX8Vepy9of0iRU= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.8.0/go.mod h1:zKU4zUgKiaRxrdovSS2amdM5gOc59slmo/zJwGX+YBg= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0 h1:SZmDnHcgp3zwlPBS2JX2urGYe/jBKEIT6ZedHRUyCz8= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.32.0/go.mod h1:fdWW0HtZJ7+jNpTKUR0GpMEDP69nR8YBJQxNiVCE3jk= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -158,8 +194,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 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.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8 h1:LoYXNGAShUG3m/ehNk4iFctuhGX/+R1ZpfJ4/ia80JM= golang.org/x/exp v0.0.0-20240604190554-fc45aab8b7f8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -170,30 +206,29 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -209,16 +244,16 @@ 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= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE= -google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo= -google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 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/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller.go b/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller.go index 0390417c9f..df283318c1 100644 --- a/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller.go +++ b/internal/bridge/crunchybridgecluster/crunchybridgecluster_controller.go @@ -25,7 +25,9 @@ import ( "github.com/crunchydata/postgres-operator/internal/bridge" "github.com/crunchydata/postgres-operator/internal/controller/runtime" "github.com/crunchydata/postgres-operator/internal/initialize" + "github.com/crunchydata/postgres-operator/internal/logging" "github.com/crunchydata/postgres-operator/internal/naming" + "github.com/crunchydata/postgres-operator/internal/tracing" "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -100,7 +102,9 @@ func (r *CrunchyBridgeClusterReconciler) setControllerReference( // Reconcile does the work to move the current state of the world toward the // desired state described in a [v1beta1.CrunchyBridgeCluster] identified by req. func (r *CrunchyBridgeClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - log := ctrl.LoggerFrom(ctx) + ctx, span := tracing.Start(ctx, "reconcile-crunchybridgecluster") + log := logging.FromContext(ctx) + defer span.End() // Retrieve the crunchybridgecluster from the client cache, if it exists. A deferred // function below will send any changes to its Status field. @@ -129,7 +133,7 @@ func (r *CrunchyBridgeClusterReconciler) Reconcile(ctx context.Context, req ctrl // NotFound cannot be fixed by requeuing so ignore it. During background // deletion, we receive delete events from crunchybridgecluster's dependents after // crunchybridgecluster is deleted. - return ctrl.Result{}, client.IgnoreNotFound(err) + return ctrl.Result{}, tracing.Escape(span, client.IgnoreNotFound(err)) } // Get and validate connection secret for requests @@ -148,12 +152,12 @@ func (r *CrunchyBridgeClusterReconciler) Reconcile(ctx context.Context, req ctrl // is not being deleted. if result, err := r.handleDelete(ctx, crunchybridgecluster, key); err != nil { log.Error(err, "deleting") - return ctrl.Result{}, err + return ctrl.Result{}, tracing.Escape(span, err) } else if result != nil { if log := log.V(1); log.Enabled() { log.Info("deleting", "result", fmt.Sprintf("%+v", *result)) } - return *result, err + return *result, tracing.Escape(span, err) } // Wonder if there's a better way to handle adding/checking/removing statuses @@ -186,7 +190,7 @@ func (r *CrunchyBridgeClusterReconciler) Reconcile(ctx context.Context, req ctrl // Check if a cluster with the same name already exists controllerResult, err := r.handleDuplicateClusterName(ctx, key, team, crunchybridgecluster) if err != nil || controllerResult != nil { - return *controllerResult, err + return *controllerResult, tracing.Escape(span, err) } // if we've gotten here then no cluster exists with that name and we're missing the ID, ergo, create cluster @@ -200,26 +204,26 @@ func (r *CrunchyBridgeClusterReconciler) Reconcile(ctx context.Context, req ctrl // Get Cluster err = r.handleGetCluster(ctx, key, crunchybridgecluster) if err != nil { - return ctrl.Result{}, err + return ctrl.Result{}, tracing.Escape(span, err) } // Get Cluster Status err = r.handleGetClusterStatus(ctx, key, crunchybridgecluster) if err != nil { - return ctrl.Result{}, err + return ctrl.Result{}, tracing.Escape(span, err) } // Get Cluster Upgrade err = r.handleGetClusterUpgrade(ctx, key, crunchybridgecluster) if err != nil { - return ctrl.Result{}, err + return ctrl.Result{}, tracing.Escape(span, err) } // Reconcile roles and their secrets err = r.reconcilePostgresRoles(ctx, key, crunchybridgecluster) if err != nil { log.Error(err, "issue reconciling postgres user roles/secrets") - return ctrl.Result{}, err + return ctrl.Result{}, tracing.Escape(span, err) } // For now, we skip updating until the upgrade status is cleared. diff --git a/internal/controller/pgupgrade/pgupgrade_controller.go b/internal/controller/pgupgrade/pgupgrade_controller.go index 0717607d7e..349a01ee89 100644 --- a/internal/controller/pgupgrade/pgupgrade_controller.go +++ b/internal/controller/pgupgrade/pgupgrade_controller.go @@ -20,7 +20,9 @@ import ( "github.com/crunchydata/postgres-operator/internal/config" "github.com/crunchydata/postgres-operator/internal/controller/runtime" + "github.com/crunchydata/postgres-operator/internal/logging" "github.com/crunchydata/postgres-operator/internal/registration" + "github.com/crunchydata/postgres-operator/internal/tracing" "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -93,7 +95,10 @@ func (r *PGUpgradeReconciler) findUpgradesForPostgresCluster( // Reconcile does the work to move the current state of the world toward the // desired state described in a [v1beta1.PGUpgrade] identified by req. func (r *PGUpgradeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { - log := ctrl.LoggerFrom(ctx) + ctx, span := tracing.Start(ctx, "reconcile-pgupgrade") + log := logging.FromContext(ctx) + defer span.End() + defer func(s tracing.Span) { _ = tracing.Escape(s, err) }(span) // Retrieve the upgrade from the client cache, if it exists. A deferred // function below will send any changes to its Status field. diff --git a/internal/controller/postgrescluster/cluster_test.go b/internal/controller/postgrescluster/cluster_test.go index 491add9f34..3ef98c58cf 100644 --- a/internal/controller/postgrescluster/cluster_test.go +++ b/internal/controller/postgrescluster/cluster_test.go @@ -8,7 +8,6 @@ import ( "context" "testing" - "go.opentelemetry.io/otel" "gotest.tools/v3/assert" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" @@ -83,7 +82,6 @@ func TestCustomLabels(t *testing.T) { Client: cc, Owner: client.FieldOwner(t.Name()), Recorder: new(record.FakeRecorder), - Tracer: otel.Tracer(t.Name()), } ns := setupNamespace(t, cc) @@ -322,7 +320,6 @@ func TestCustomAnnotations(t *testing.T) { Client: cc, Owner: client.FieldOwner(t.Name()), Recorder: new(record.FakeRecorder), - Tracer: otel.Tracer(t.Name()), } ns := setupNamespace(t, cc) diff --git a/internal/controller/postgrescluster/controller.go b/internal/controller/postgrescluster/controller.go index 394c87a750..933b781815 100644 --- a/internal/controller/postgrescluster/controller.go +++ b/internal/controller/postgrescluster/controller.go @@ -11,7 +11,6 @@ import ( "io" "time" - "go.opentelemetry.io/otel/trace" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -40,6 +39,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/pki" "github.com/crunchydata/postgres-operator/internal/postgres" "github.com/crunchydata/postgres-operator/internal/registration" + "github.com/crunchydata/postgres-operator/internal/tracing" "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -58,7 +58,6 @@ type Reconciler struct { ) error Recorder record.EventRecorder Registration registration.Registration - Tracer trace.Tracer } // +kubebuilder:rbac:groups="",resources="events",verbs={create,patch} @@ -69,7 +68,7 @@ type Reconciler struct { func (r *Reconciler) Reconcile( ctx context.Context, request reconcile.Request) (reconcile.Result, error, ) { - ctx, span := r.Tracer.Start(ctx, "Reconcile") + ctx, span := tracing.Start(ctx, "reconcile-postgrescluster") log := logging.FromContext(ctx) defer span.End() @@ -81,9 +80,8 @@ func (r *Reconciler) Reconcile( // cluster is deleted. if err = client.IgnoreNotFound(err); err != nil { log.Error(err, "unable to fetch PostgresCluster") - span.RecordError(err) } - return runtime.ErrorWithBackoff(err) + return runtime.ErrorWithBackoff(tracing.Escape(span, err)) } // Set any defaults that may not have been stored in the API. No DeepCopy @@ -108,9 +106,8 @@ func (r *Reconciler) Reconcile( // Check for and handle deletion of cluster. Return early if it is being // deleted or there was an error. if result, err := r.handleDelete(ctx, cluster); err != nil { - span.RecordError(err) log.Error(err, "deleting") - return runtime.ErrorWithBackoff(err) + return runtime.ErrorWithBackoff(tracing.Escape(span, err)) } else if result != nil { if log := log.V(1); log.Enabled() { @@ -131,7 +128,7 @@ func (r *Reconciler) Reconcile( // specifically allow reconciliation if the cluster is shutdown to // facilitate upgrades, otherwise return if !initialize.FromPointer(cluster.Spec.Shutdown) { - return runtime.ErrorWithBackoff(err) + return runtime.ErrorWithBackoff(tracing.Escape(span, err)) } } // Issue Warning Event if postgres version is EOL according to PostgreSQL: @@ -155,7 +152,7 @@ func (r *Reconciler) Reconcile( path := field.NewPath("spec", "standby") err := field.Invalid(path, cluster.Name, "Standby requires a host or repoName to be enabled") r.Recorder.Event(cluster, corev1.EventTypeWarning, "InvalidStandbyConfiguration", err.Error()) - return runtime.ErrorWithBackoff(err) + return runtime.ErrorWithBackoff(tracing.Escape(span, err)) } var ( @@ -209,7 +206,7 @@ func (r *Reconciler) Reconcile( ObservedGeneration: cluster.GetGeneration(), }) - return runtime.ErrorWithBackoff(patchClusterStatus()) + return runtime.ErrorWithBackoff(tracing.Escape(span, patchClusterStatus())) } else { meta.RemoveStatusCondition(&cluster.Status.Conditions, v1beta1.PostgresClusterProgressing) } @@ -229,7 +226,7 @@ func (r *Reconciler) Reconcile( ObservedGeneration: cluster.GetGeneration(), }) - return runtime.ErrorWithBackoff(patchClusterStatus()) + return runtime.ErrorWithBackoff(tracing.Escape(span, patchClusterStatus())) } else { meta.RemoveStatusCondition(&cluster.Status.Conditions, v1beta1.PostgresClusterProgressing) } @@ -260,7 +257,8 @@ func (r *Reconciler) Reconcile( // return is no longer needed, and reconciliation can proceed normally. returnEarly, err := r.reconcileDirMoveJobs(ctx, cluster) if err != nil || returnEarly { - return runtime.ErrorWithBackoff(errors.Join(err, patchClusterStatus())) + return runtime.ErrorWithBackoff(tracing.Escape(span, + errors.Join(err, patchClusterStatus()))) } } if err == nil { @@ -310,7 +308,7 @@ func (r *Reconciler) Reconcile( // can proceed normally. returnEarly, err := r.reconcileDataSource(ctx, cluster, instances, clusterVolumes, rootCA, backupsSpecFound) if err != nil || returnEarly { - return runtime.ErrorWithBackoff(errors.Join(err, patchClusterStatus())) + return runtime.ErrorWithBackoff(tracing.Escape(span, errors.Join(err, patchClusterStatus()))) } } if err == nil { @@ -402,7 +400,7 @@ func (r *Reconciler) Reconcile( log.V(1).Info("reconciled cluster") - return result, errors.Join(err, patchClusterStatus()) + return result, tracing.Escape(span, errors.Join(err, patchClusterStatus())) } // deleteControlled safely deletes object when it is controlled by cluster. diff --git a/internal/controller/postgrescluster/controller_test.go b/internal/controller/postgrescluster/controller_test.go index b9e928ecce..6def47556e 100644 --- a/internal/controller/postgrescluster/controller_test.go +++ b/internal/controller/postgrescluster/controller_test.go @@ -15,7 +15,6 @@ import ( . "github.com/onsi/gomega/gstruct" "github.com/pkg/errors" //nolint:depguard // This legacy test covers so much code, it logs the origin of unexpected errors. - "go.opentelemetry.io/otel" "gotest.tools/v3/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -138,7 +137,6 @@ var _ = Describe("PostgresCluster Reconciler", func() { test.Reconciler.Owner = "asdf" test.Reconciler.Recorder = test.Recorder test.Reconciler.Registration = nil - test.Reconciler.Tracer = otel.Tracer("asdf") }) AfterEach(func() { diff --git a/internal/controller/postgrescluster/instance.go b/internal/controller/postgrescluster/instance.go index ff3810ae3c..97cc2cdce5 100644 --- a/internal/controller/postgrescluster/instance.go +++ b/internal/controller/postgrescluster/instance.go @@ -13,8 +13,6 @@ import ( "time" "github.com/pkg/errors" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" @@ -36,6 +34,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/pgbackrest" "github.com/crunchydata/postgres-operator/internal/pki" "github.com/crunchydata/postgres-operator/internal/postgres" + "github.com/crunchydata/postgres-operator/internal/tracing" "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -800,8 +799,7 @@ func (r *Reconciler) rolloutInstance( // NOTE(cbandy): The StatefulSet controlling this Pod reflects this change // in its Status and triggers another reconcile. if primary && len(instances.forCluster) > 1 { - var span trace.Span - ctx, span = r.Tracer.Start(ctx, "patroni-change-primary") + ctx, span := tracing.Start(ctx, "patroni-change-primary") defer span.End() success, err := patroni.Executor(exec).ChangePrimaryAndWait(ctx, pod.Name, "") @@ -809,8 +807,7 @@ func (r *Reconciler) rolloutInstance( err = errors.New("unable to switchover") } - span.RecordError(err) - return err + return tracing.Escape(span, err) } // When the cluster has only one instance for failover, perform a series of @@ -824,7 +821,7 @@ func (r *Reconciler) rolloutInstance( } checkpoint := func(ctx context.Context) (time.Duration, error) { - ctx, span := r.Tracer.Start(ctx, "postgresql-checkpoint") + ctx, span := tracing.Start(ctx, "postgresql-checkpoint") defer span.End() start := time.Now() @@ -842,8 +839,7 @@ func (r *Reconciler) rolloutInstance( logging.FromContext(ctx).V(1).Info("attempted checkpoint", "duration", elapsed, "stdout", stdout, "stderr", stderr) - span.RecordError(err) - return elapsed, err + return elapsed, tracing.Escape(span, err) } duration, err := checkpoint(ctx) @@ -894,7 +890,7 @@ func (r *Reconciler) rolloutInstances( var numAvailable int var numSpecified int - ctx, span := r.Tracer.Start(ctx, "rollout-instances") + ctx, span := tracing.Start(ctx, "rollout-instances") defer span.End() for _, set := range cluster.Spec.InstanceSets { @@ -933,12 +929,10 @@ func (r *Reconciler) rolloutInstances( sort.Sort(byPriority(consider)) } - span.SetAttributes( - attribute.Int("instances", len(instances.forCluster)), - attribute.Int("specified", numSpecified), - attribute.Int("available", numAvailable), - attribute.Int("considering", len(consider)), - ) + tracing.Int(span, "instances", len(instances.forCluster)) + tracing.Int(span, "specified", numSpecified) + tracing.Int(span, "available", numAvailable) + tracing.Int(span, "considering", len(consider)) // Redeploy instances up to the allowed maximum while "rolling over" any // unavailable instances. @@ -954,8 +948,7 @@ func (r *Reconciler) rolloutInstances( } } - span.RecordError(err) - return err + return tracing.Escape(span, err) } // scaleDownInstances removes extra instances from a cluster until it matches @@ -1085,21 +1078,23 @@ func (r *Reconciler) scaleUpInstances( // While there are fewer instances than specified, generate another empty one // and append it. for len(instances) < int(*set.Replicas) { - var span trace.Span - ctx, span = r.Tracer.Start(ctx, "generateInstanceName") - next := naming.GenerateInstance(cluster, set) - // if there are any available instance names (as determined by observing any PVCs for the - // instance set that are not currently associated with an instance, e.g. in the event the - // instance STS was deleted), then reuse them instead of generating a new name - if len(availableInstanceNames) > 0 { - next.Name = availableInstanceNames[0] - availableInstanceNames = availableInstanceNames[1:] - } else { - for instanceNames.Has(next.Name) { - next = naming.GenerateInstance(cluster, set) + next := func() metav1.ObjectMeta { + _, span := tracing.Start(ctx, "generate-instance-name") + defer span.End() + n := naming.GenerateInstance(cluster, set) + // if there are any available instance names (as determined by observing any PVCs for the + // instance set that are not currently associated with an instance, e.g. in the event the + // instance STS was deleted), then reuse them instead of generating a new name + if len(availableInstanceNames) > 0 { + n.Name = availableInstanceNames[0] + availableInstanceNames = availableInstanceNames[1:] + } else { + for instanceNames.Has(n.Name) { + n = naming.GenerateInstance(cluster, set) + } } - } - span.End() + return n + }() instanceNames.Insert(next.Name) instances = append(instances, &appsv1.StatefulSet{ObjectMeta: next}) diff --git a/internal/controller/postgrescluster/instance_rollout_test.go b/internal/controller/postgrescluster/instance_rollout_test.go index e668907497..bede908615 100644 --- a/internal/controller/postgrescluster/instance_rollout_test.go +++ b/internal/controller/postgrescluster/instance_rollout_test.go @@ -10,7 +10,6 @@ import ( "strings" "testing" - "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" @@ -25,6 +24,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/initialize" "github.com/crunchydata/postgres-operator/internal/testing/cmp" + "github.com/crunchydata/postgres-operator/internal/tracing" "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -60,7 +60,6 @@ func TestReconcilerRolloutInstance(t *testing.T) { key := client.ObjectKey{Namespace: "ns1", Name: "one-pod-bruh"} reconciler := &Reconciler{} reconciler.Client = fake.NewClientBuilder().WithObjects(instances[0].Pods[0]).Build() - reconciler.Tracer = otel.Tracer(t.Name()) execCalls := 0 reconciler.PodExec = func( @@ -121,7 +120,6 @@ func TestReconcilerRolloutInstance(t *testing.T) { t.Run("Success", func(t *testing.T) { execCalls := 0 reconciler := &Reconciler{} - reconciler.Tracer = otel.Tracer(t.Name()) reconciler.PodExec = func( ctx context.Context, namespace, pod, container string, _ io.Reader, stdout, _ io.Writer, command ...string, ) error { @@ -149,7 +147,6 @@ func TestReconcilerRolloutInstance(t *testing.T) { t.Run("Failure", func(t *testing.T) { reconciler := &Reconciler{} - reconciler.Tracer = otel.Tracer(t.Name()) reconciler.PodExec = func( ctx context.Context, _, _, _ string, _ io.Reader, _, _ io.Writer, _ ...string, ) error { @@ -165,26 +162,25 @@ func TestReconcilerRolloutInstance(t *testing.T) { func TestReconcilerRolloutInstances(t *testing.T) { ctx := context.Background() - reconciler := &Reconciler{Tracer: otel.Tracer(t.Name())} + reconciler := &Reconciler{} accumulate := func(on *[]*Instance) func(context.Context, *Instance) error { return func(_ context.Context, i *Instance) error { *on = append(*on, i); return nil } } - logSpanAttributes := func(t testing.TB) { + logSpanAttributes := func(t testing.TB, ctx context.Context) context.Context { recorder := tracetest.NewSpanRecorder() provider := trace.NewTracerProvider(trace.WithSpanProcessor(recorder)) - - former := reconciler.Tracer - reconciler.Tracer = provider.Tracer(t.Name()) + tracer := provider.Tracer(t.Name()) t.Cleanup(func() { - reconciler.Tracer = former for _, span := range recorder.Ended() { attr := attribute.NewSet(span.Attributes()...) t.Log(span.Name(), attr.Encoded(attribute.DefaultEncoder())) } }) + + return tracing.NewContext(ctx, tracer) } // Nothing specified, nothing observed, nothing to do. @@ -192,7 +188,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { cluster := new(v1beta1.PostgresCluster) observed := new(observedInstances) - logSpanAttributes(t) + ctx := logSpanAttributes(t, ctx) assert.NilError(t, reconciler.rolloutInstances(ctx, cluster, observed, func(context.Context, *Instance) error { t.Fatal("expected no redeploys") @@ -237,7 +233,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { } observed := &observedInstances{forCluster: instances} - logSpanAttributes(t) + ctx := logSpanAttributes(t, ctx) assert.NilError(t, reconciler.rolloutInstances(ctx, cluster, observed, func(context.Context, *Instance) error { t.Fatal("expected no redeploys") @@ -284,7 +280,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { var redeploys []*Instance - logSpanAttributes(t) + ctx := logSpanAttributes(t, ctx) assert.NilError(t, reconciler.rolloutInstances(ctx, cluster, observed, accumulate(&redeploys))) assert.Equal(t, len(redeploys), 1) assert.Equal(t, redeploys[0].Name, "one") @@ -354,7 +350,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { var redeploys []*Instance - logSpanAttributes(t) + ctx := logSpanAttributes(t, ctx) assert.NilError(t, reconciler.rolloutInstances(ctx, cluster, observed, accumulate(&redeploys))) assert.Equal(t, len(redeploys), 1) assert.Equal(t, redeploys[0].Name, "one", `expected the "lowest" name`) @@ -425,7 +421,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { var redeploys []*Instance - logSpanAttributes(t) + ctx := logSpanAttributes(t, ctx) assert.NilError(t, reconciler.rolloutInstances(ctx, cluster, observed, accumulate(&redeploys))) assert.Equal(t, len(redeploys), 1) assert.Equal(t, redeploys[0].Name, "not-primary") @@ -495,7 +491,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { var redeploys []*Instance - logSpanAttributes(t) + ctx := logSpanAttributes(t, ctx) assert.NilError(t, reconciler.rolloutInstances(ctx, cluster, observed, accumulate(&redeploys))) assert.Equal(t, len(redeploys), 1) assert.Equal(t, redeploys[0].Name, "not-ready") @@ -564,7 +560,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { } observed := &observedInstances{forCluster: instances} - logSpanAttributes(t) + ctx := logSpanAttributes(t, ctx) assert.NilError(t, reconciler.rolloutInstances(ctx, cluster, observed, func(context.Context, *Instance) error { t.Fatal("expected no redeploys") @@ -633,7 +629,7 @@ func TestReconcilerRolloutInstances(t *testing.T) { } observed := &observedInstances{forCluster: instances} - logSpanAttributes(t) + ctx := logSpanAttributes(t, ctx) assert.NilError(t, reconciler.rolloutInstances(ctx, cluster, observed, func(context.Context, *Instance) error { t.Fatal("expected no redeploys") diff --git a/internal/controller/postgrescluster/instance_test.go b/internal/controller/postgrescluster/instance_test.go index f4eda5b056..064714872f 100644 --- a/internal/controller/postgrescluster/instance_test.go +++ b/internal/controller/postgrescluster/instance_test.go @@ -16,7 +16,6 @@ import ( "github.com/go-logr/logr/funcr" "github.com/google/go-cmp/cmp/cmpopts" - "go.opentelemetry.io/otel" "gotest.tools/v3/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -1339,7 +1338,6 @@ func TestDeleteInstance(t *testing.T) { Client: cc, Owner: client.FieldOwner(t.Name()), Recorder: new(record.FakeRecorder), - Tracer: otel.Tracer(t.Name()), } // Define, Create, and Reconcile a cluster to get an instance running in kube diff --git a/internal/controller/postgrescluster/pgbackrest_test.go b/internal/controller/postgrescluster/pgbackrest_test.go index c078f37d8a..b7855f1732 100644 --- a/internal/controller/postgrescluster/pgbackrest_test.go +++ b/internal/controller/postgrescluster/pgbackrest_test.go @@ -15,7 +15,6 @@ import ( "testing" "time" - "go.opentelemetry.io/otel" "gotest.tools/v3/assert" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" @@ -180,7 +179,6 @@ func TestReconcilePGBackRest(t *testing.T) { r = &Reconciler{ Client: mgr.GetClient(), Recorder: mgr.GetEventRecorderFor(ControllerName), - Tracer: otel.Tracer(ControllerName), Owner: ControllerName, } }) @@ -735,7 +733,6 @@ func TestReconcileStanzaCreate(t *testing.T) { r = &Reconciler{ Client: mgr.GetClient(), Recorder: mgr.GetEventRecorderFor(ControllerName), - Tracer: otel.Tracer(ControllerName), Owner: ControllerName, } }) @@ -1014,7 +1011,6 @@ func TestReconcileManualBackup(t *testing.T) { r = &Reconciler{ Client: mgr.GetClient(), Recorder: mgr.GetEventRecorderFor(ControllerName), - Tracer: otel.Tracer(ControllerName), Owner: ControllerName, } }) @@ -1724,7 +1720,6 @@ func TestReconcilePostgresClusterDataSource(t *testing.T) { r = &Reconciler{ Client: tClient, Recorder: mgr.GetEventRecorderFor(ControllerName), - Tracer: otel.Tracer(ControllerName), Owner: ControllerName, } }) @@ -2018,7 +2013,6 @@ func TestReconcileCloudBasedDataSource(t *testing.T) { r = &Reconciler{ Client: tClient, Recorder: mgr.GetEventRecorderFor(ControllerName), - Tracer: otel.Tracer(ControllerName), Owner: ControllerName, } }) @@ -3393,7 +3387,6 @@ func TestReconcileScheduledBackups(t *testing.T) { r = &Reconciler{ Client: mgr.GetClient(), Recorder: mgr.GetEventRecorderFor(ControllerName), - Tracer: otel.Tracer(ControllerName), Owner: ControllerName, } }) @@ -3730,7 +3723,6 @@ func TestBackupsEnabled(t *testing.T) { r = &Reconciler{ Client: mgr.GetClient(), Recorder: mgr.GetEventRecorderFor(ControllerName), - Tracer: otel.Tracer(ControllerName), Owner: ControllerName, } }) diff --git a/internal/controller/runtime/runtime.go b/internal/controller/runtime/runtime.go index 34bfeabf61..51fc37bf0d 100644 --- a/internal/controller/runtime/runtime.go +++ b/internal/controller/runtime/runtime.go @@ -5,8 +5,6 @@ package runtime import ( - "context" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -14,7 +12,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/manager/signals" "github.com/crunchydata/postgres-operator/internal/logging" "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" @@ -71,6 +68,3 @@ func NewManager(config *rest.Config, options manager.Options) (manager.Manager, // SetLogger assigns the default Logger used by [sigs.k8s.io/controller-runtime]. func SetLogger(logger logging.Logger) { log.SetLogger(logger) } - -// SignalHandler returns a Context that is canceled on SIGINT or SIGTERM. -func SignalHandler() context.Context { return signals.SetupSignalHandler() } diff --git a/internal/controller/standalone_pgadmin/controller.go b/internal/controller/standalone_pgadmin/controller.go index d16c33b797..7e1005900c 100644 --- a/internal/controller/standalone_pgadmin/controller.go +++ b/internal/controller/standalone_pgadmin/controller.go @@ -20,6 +20,7 @@ import ( "github.com/crunchydata/postgres-operator/internal/controller/runtime" "github.com/crunchydata/postgres-operator/internal/logging" + "github.com/crunchydata/postgres-operator/internal/tracing" "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" ) @@ -83,14 +84,16 @@ func (r *PGAdminReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *PGAdminReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { var err error + ctx, span := tracing.Start(ctx, "reconcile-pgadmin") log := logging.FromContext(ctx) + defer span.End() pgAdmin := &v1beta1.PGAdmin{} if err := r.Get(ctx, req.NamespacedName, pgAdmin); err != nil { // NotFound cannot be fixed by requeuing so ignore it. During background // deletion, we receive delete events from pgadmin's dependents after // pgadmin is deleted. - return ctrl.Result{}, client.IgnoreNotFound(err) + return ctrl.Result{}, tracing.Escape(span, client.IgnoreNotFound(err)) } // Write any changes to the pgadmin status on the way out. @@ -145,7 +148,7 @@ func (r *PGAdminReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct log.V(1).Info("Reconciled pgAdmin") } - return ctrl.Result{}, err + return ctrl.Result{}, tracing.Escape(span, err) } // The owner reference created by controllerutil.SetControllerReference blocks diff --git a/internal/kubernetes/discovery.go b/internal/kubernetes/discovery.go index ddc8d2cc3a..471e5360ea 100644 --- a/internal/kubernetes/discovery.go +++ b/internal/kubernetes/discovery.go @@ -26,6 +26,7 @@ type Version = version.Info type DiscoveryRunner struct { // NOTE(tracing): The methods of [discovery.DiscoveryClient] do not take // a Context so their API calls won't have a parent span. + // - https://issue.k8s.io/126379 Client interface { ServerGroups() (*metav1.APIGroupList, error) ServerResourcesForGroupVersion(string) (*metav1.APIResourceList, error) diff --git a/internal/logging/logr.go b/internal/logging/logr.go index 7d6f208744..4d82294dd6 100644 --- a/internal/logging/logr.go +++ b/internal/logging/logr.go @@ -37,10 +37,12 @@ func FromContext(ctx context.Context) Logger { } // Add trace context, if any, according to OpenTelemetry recommendations. - // Omit trace flags for now because they don't seem relevant. - // - https://github.com/open-telemetry/opentelemetry-specification/blob/v0.7.0/specification/logs/overview.md + // - https://github.com/open-telemetry/opentelemetry-specification/blob/v1.39.0/specification/compatibility/logging_trace_context.md if sc := trace.SpanFromContext(ctx).SpanContext(); sc.IsValid() { - log = log.WithValues("spanid", sc.SpanID(), "traceid", sc.TraceID()) + log = log.WithValues( + "span_id", sc.SpanID(), + "trace_id", sc.TraceID(), + "trace_flags", sc.TraceFlags()) } return log diff --git a/internal/logging/logr_test.go b/internal/logging/logr_test.go index 1cbc818ad9..5b78c1dd7a 100644 --- a/internal/logging/logr_test.go +++ b/internal/logging/logr_test.go @@ -31,11 +31,11 @@ func TestFromContext(t *testing.T) { } func TestFromContextTraceContext(t *testing.T) { - var calls []map[string]interface{} + var calls []map[string]any SetLogSink(&sink{ - fnInfo: func(_ int, _ string, kv ...interface{}) { - m := make(map[string]interface{}) + fnInfo: func(_ int, _ string, kv ...any) { + m := make(map[string]any) for i := 0; i < len(kv); i += 2 { m[kv[i].(string)] = kv[i+1] } @@ -47,23 +47,23 @@ func TestFromContextTraceContext(t *testing.T) { // Nothing when there's no trace. FromContext(ctx).Info("") - assert.Equal(t, calls[0]["spanid"], nil) - assert.Equal(t, calls[0]["traceid"], nil) + assert.Equal(t, calls[0]["span_id"], nil) + assert.Equal(t, calls[0]["trace_id"], nil) ctx, span := trace.NewTracerProvider().Tracer("").Start(ctx, "test-span") defer span.End() // OpenTelemetry trace context when there is. FromContext(ctx).Info("") - assert.Equal(t, calls[1]["spanid"], span.SpanContext().SpanID()) - assert.Equal(t, calls[1]["traceid"], span.SpanContext().TraceID()) + assert.Equal(t, calls[1]["span_id"], span.SpanContext().SpanID()) + assert.Equal(t, calls[1]["trace_id"], span.SpanContext().TraceID()) } func TestSetLogSink(t *testing.T) { var calls []string SetLogSink(&sink{ - fnInfo: func(_ int, m string, _ ...interface{}) { + fnInfo: func(_ int, m string, _ ...any) { calls = append(calls, m) }, }) diff --git a/internal/naming/dns.go b/internal/naming/dns.go index d3351a5d70..3925bfe988 100644 --- a/internal/naming/dns.go +++ b/internal/naming/dns.go @@ -11,6 +11,8 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + + "github.com/crunchydata/postgres-operator/internal/tracing" ) // InstancePodDNSNames returns the possible DNS names for instance. The first @@ -68,7 +70,7 @@ func ServiceDNSNames(ctx context.Context, service *corev1.Service) []string { // KubernetesClusterDomain looks up the Kubernetes cluster domain name. func KubernetesClusterDomain(ctx context.Context) string { - ctx, span := tracer.Start(ctx, "kubernetes-domain-lookup") + ctx, span := tracing.Start(ctx, "kubernetes-domain-lookup") defer span.End() // Lookup an existing Service to determine its fully qualified domain name. @@ -77,11 +79,10 @@ func KubernetesClusterDomain(ctx context.Context) string { api := "kubernetes.default.svc" cname, err := net.DefaultResolver.LookupCNAME(ctx, api) - if err == nil { + if tracing.Check(span, err) { return strings.TrimPrefix(cname, api+".") } - span.RecordError(err) // The kubeadm default is "cluster.local" and is adequate when not running // in an actual Kubernetes cluster. return "cluster.local." diff --git a/internal/naming/telemetry.go b/internal/naming/telemetry.go deleted file mode 100644 index 5825d6299f..0000000000 --- a/internal/naming/telemetry.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. -// -// SPDX-License-Identifier: Apache-2.0 - -package naming - -import "go.opentelemetry.io/otel" - -var tracer = otel.Tracer("github.com/crunchydata/postgres-operator/naming") diff --git a/internal/tracing/errors.go b/internal/tracing/errors.go new file mode 100644 index 0000000000..d0e00cf56c --- /dev/null +++ b/internal/tracing/errors.go @@ -0,0 +1,34 @@ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" + "go.opentelemetry.io/otel/trace" +) + +// Check returns true when err is nil. Otherwise, it adds err as an exception +// event on s and returns false. If you intend to return err, consider using +// [Escape] instead. +// +// See: https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans +func Check(s Span, err error) bool { + if err == nil { + return true + } + if s.IsRecording() { + s.RecordError(err) + } + return false +} + +// Escape adds non-nil err as an escaped exception event on s and returns err. +// See: https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans +func Escape(s Span, err error) error { + if err != nil && s.IsRecording() { + s.RecordError(err, trace.WithAttributes(semconv.ExceptionEscaped(true))) + } + return err +} diff --git a/internal/tracing/errors_test.go b/internal/tracing/errors_test.go new file mode 100644 index 0000000000..4f8f6d1be5 --- /dev/null +++ b/internal/tracing/errors_test.go @@ -0,0 +1,94 @@ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "context" + "errors" + "testing" + + "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" + "gotest.tools/v3/assert" +) + +func TestCheck(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + tracer := trace.NewTracerProvider( + trace.WithSpanProcessor(recorder), + ).Tracer("") + + { + _, span := tracer.Start(context.Background(), "") + assert.Assert(t, Check(span, nil)) + span.End() + + spans := recorder.Ended() + assert.Equal(t, len(spans), 1) + assert.Equal(t, len(spans[0].Events()), 0, "expected no events") + } + + { + _, span := tracer.Start(context.Background(), "") + assert.Assert(t, !Check(span, errors.New("msg"))) + span.End() + + spans := recorder.Ended() + assert.Equal(t, len(spans), 2) + assert.Equal(t, len(spans[1].Events()), 1, "expected one event") + + event := spans[1].Events()[0] + assert.Equal(t, event.Name, semconv.ExceptionEventName) + + attrs := event.Attributes + assert.Equal(t, len(attrs), 2) + assert.Equal(t, string(attrs[0].Key), "exception.type") + assert.Equal(t, string(attrs[1].Key), "exception.message") + assert.Equal(t, attrs[0].Value.AsInterface(), "*errors.errorString") + assert.Equal(t, attrs[1].Value.AsInterface(), "msg") + } +} + +func TestEscape(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + tracer := trace.NewTracerProvider( + trace.WithSpanProcessor(recorder), + ).Tracer("") + + { + _, span := tracer.Start(context.Background(), "") + assert.NilError(t, Escape(span, nil)) + span.End() + + spans := recorder.Ended() + assert.Equal(t, len(spans), 1) + assert.Equal(t, len(spans[0].Events()), 0, "expected no events") + } + + { + _, span := tracer.Start(context.Background(), "") + expected := errors.New("somesuch") + assert.Assert(t, errors.Is(Escape(span, expected), expected), + "expected to unwrap the original error") + span.End() + + spans := recorder.Ended() + assert.Equal(t, len(spans), 2) + assert.Equal(t, len(spans[1].Events()), 1, "expected one event") + + event := spans[1].Events()[0] + assert.Equal(t, event.Name, semconv.ExceptionEventName) + + attrs := event.Attributes + assert.Equal(t, len(attrs), 3) + assert.Equal(t, string(attrs[0].Key), "exception.escaped") + assert.Equal(t, string(attrs[1].Key), "exception.type") + assert.Equal(t, string(attrs[2].Key), "exception.message") + assert.Equal(t, attrs[0].Value.AsInterface(), true) + assert.Equal(t, attrs[1].Value.AsInterface(), "*errors.errorString") + assert.Equal(t, attrs[2].Value.AsInterface(), "somesuch") + } +} diff --git a/internal/tracing/tracing.go b/internal/tracing/tracing.go new file mode 100644 index 0000000000..f7f722c8db --- /dev/null +++ b/internal/tracing/tracing.go @@ -0,0 +1,65 @@ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "context" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" +) + +// https://pkg.go.dev/go.opentelemetry.io/otel/trace +type ( + Span = trace.Span + Tracer = trace.Tracer +) + +var global = noop.NewTracerProvider().Tracer("") + +// SetDefaultTracer replaces the default Tracer with t. Before this is called, +// the default Tracer is a no-op. +func SetDefaultTracer(t Tracer) { global = t } + +type tracerKey struct{} + +// FromContext returns the Tracer stored by a prior call to [WithTracer] or [SetDefaultTracer]. +func FromContext(ctx context.Context) Tracer { + if t, ok := ctx.Value(tracerKey{}).(Tracer); ok { + return t + } + return global +} + +// NewContext returns a copy of ctx containing t. Retrieve it using [FromContext]. +func NewContext(ctx context.Context, t Tracer) context.Context { + return context.WithValue(ctx, tracerKey{}, t) +} + +// New returns a Tracer produced by [otel.GetTracerProvider]. +func New(name string, opts ...trace.TracerOption) Tracer { + opts = append([]trace.TracerOption{ + trace.WithSchemaURL(semconv.SchemaURL), + }, opts...) + + return otel.GetTracerProvider().Tracer(name, opts...) +} + +// Start creates a Span and a Context containing it. It uses the Tracer returned by [FromContext]. +func Start(ctx context.Context, name string, opts ...trace.SpanStartOption) (context.Context, Span) { + return FromContext(ctx).Start(ctx, name, opts...) +} + +// Bool sets the k attribute of s to v. +func Bool(s Span, k string, v bool) { s.SetAttributes(attribute.Bool(k, v)) } + +// Int sets the k attribute of s to v. +func Int(s Span, k string, v int) { s.SetAttributes(attribute.Int(k, v)) } + +// String sets the k attribute of s to v. +func String(s Span, k, v string) { s.SetAttributes(attribute.String(k, v)) } diff --git a/internal/tracing/tracing_test.go b/internal/tracing/tracing_test.go new file mode 100644 index 0000000000..e9d519a71c --- /dev/null +++ b/internal/tracing/tracing_test.go @@ -0,0 +1,110 @@ +// Copyright 2021 - 2024 Crunchy Data Solutions, Inc. +// +// SPDX-License-Identifier: Apache-2.0 + +package tracing + +import ( + "context" + "testing" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + semconv "go.opentelemetry.io/otel/semconv/v1.27.0" + "gotest.tools/v3/assert" +) + +func TestDefaultTracer(t *testing.T) { + ctx := context.Background() + + t.Run("no-op", func(t *testing.T) { + tracer := FromContext(ctx) + _, s1 := tracer.Start(ctx, "asdf") + defer s1.End() + assert.Assert(t, !s1.IsRecording()) + + _, s2 := Start(ctx, "doot") + defer s2.End() + assert.Assert(t, !s2.IsRecording()) + }) + + t.Run("set", func(t *testing.T) { + prior := global + t.Cleanup(func() { SetDefaultTracer(prior) }) + + recorder := tracetest.NewSpanRecorder() + SetDefaultTracer(trace.NewTracerProvider( + trace.WithSpanProcessor(recorder), + ).Tracer("myst")) + + _, span := Start(ctx, "zork") + span.End() + + spans := recorder.Ended() + assert.Equal(t, len(spans), 1) + assert.Equal(t, spans[0].InstrumentationScope().Name, "myst") + assert.Equal(t, spans[0].Name(), "zork") + }) +} + +func TestNew(t *testing.T) { + prior := otel.GetTracerProvider() + t.Cleanup(func() { otel.SetTracerProvider(prior) }) + + recorder := tracetest.NewSpanRecorder() + otel.SetTracerProvider(trace.NewTracerProvider( + trace.WithSpanProcessor(recorder), + )) + + _, span := New("onetwo").Start(context.Background(), "three") + span.End() + + spans := recorder.Ended() + assert.Equal(t, len(spans), 1) + assert.Equal(t, spans[0].InstrumentationScope().Name, "onetwo") + assert.Equal(t, spans[0].InstrumentationScope().SchemaURL, semconv.SchemaURL) + assert.Equal(t, spans[0].Name(), "three") +} + +func TestFromContext(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + + ctx := NewContext(context.Background(), trace.NewTracerProvider( + trace.WithSpanProcessor(recorder), + ).Tracer("something")) + + _, span := Start(ctx, "spanspan") + span.End() + + spans := recorder.Ended() + assert.Equal(t, len(spans), 1) + assert.Equal(t, spans[0].InstrumentationScope().Name, "something") + assert.Equal(t, spans[0].Name(), "spanspan") +} + +func TestAttributes(t *testing.T) { + recorder := tracetest.NewSpanRecorder() + + ctx := NewContext(context.Background(), trace.NewTracerProvider( + trace.WithSpanProcessor(recorder), + ).Tracer("")) + + _, span := Start(ctx, "") + Bool(span, "aa", true) + Int(span, "abc", 99) + String(span, "xyz", "copy pasta") + span.End() + + spans := recorder.Ended() + assert.Equal(t, len(spans), 1) + assert.Equal(t, len(spans[0].Attributes()), 3) + + attrs := spans[0].Attributes() + assert.Equal(t, string(attrs[0].Key), "aa") + assert.Equal(t, string(attrs[1].Key), "abc") + assert.Equal(t, string(attrs[2].Key), "xyz") + assert.Equal(t, attrs[0].Value.AsInterface(), true) + assert.Equal(t, attrs[1].Value.AsInterface(), int64(99)) + assert.Equal(t, attrs[2].Value.AsInterface(), "copy pasta") +}