package observability import ( "context" "fmt" "sync" "time" "github.com/sirupsen/logrus" "github.com/supabase/auth/internal/conf" "github.com/supabase/auth/internal/utilities" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/propagation" sdkresource "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace" ) func Tracer(name string, opts ...trace.TracerOption) trace.Tracer { return otel.Tracer(name, opts...) } func openTelemetryResource() *sdkresource.Resource { environmentResource := sdkresource.Environment() gotrueResource := sdkresource.NewSchemaless(attribute.String("gotrue.version", utilities.Version)) mergedResource, err := sdkresource.Merge(environmentResource, gotrueResource) if err != nil { logrus.WithError(err).Error("unable to merge OpenTelemetry environment and gotrue resources") return environmentResource } return mergedResource } func enableOpenTelemetryTracing(ctx context.Context, tc *conf.TracingConfig) error { var ( err error traceExporter *otlptrace.Exporter ) switch tc.ExporterProtocol { case "grpc": traceExporter, err = otlptracegrpc.New(ctx) if err != nil { return err } case "http/protobuf": traceExporter, err = otlptracehttp.New(ctx) if err != nil { return err } default: // http/json for example return fmt.Errorf("unsupported OpenTelemetry exporter protocol %q", tc.ExporterProtocol) } traceProvider := sdktrace.NewTracerProvider( sdktrace.WithBatcher(traceExporter), sdktrace.WithResource(openTelemetryResource()), ) otel.SetTracerProvider(traceProvider) // Register the W3C trace context and baggage propagators so data is // propagated across services/processes otel.SetTextMapPropagator( propagation.NewCompositeTextMapPropagator( propagation.TraceContext{}, propagation.Baggage{}, ), ) cleanupWaitGroup.Add(1) go func() { defer cleanupWaitGroup.Done() <-ctx.Done() shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second) defer shutdownCancel() if err := traceExporter.Shutdown(shutdownCtx); err != nil { logrus.WithError(err).Error("unable to shutdown OpenTelemetry trace exporter") } if err := traceProvider.Shutdown(shutdownCtx); err != nil { logrus.WithError(err).Error("unable to shutdown OpenTelemetry trace provider") } }() logrus.Info("OpenTelemetry trace exporter started") return nil } var ( tracingOnce sync.Once ) // ConfigureTracing sets up global tracing configuration for OpenTracing / // OpenTelemetry. The context should be the global context. Cancelling this // context will cancel tracing collection. func ConfigureTracing(ctx context.Context, tc *conf.TracingConfig) error { if ctx == nil { panic("context must not be nil") } var err error tracingOnce.Do(func() { if tc.Enabled { if tc.Exporter == conf.OpenTelemetryTracing { if err = enableOpenTelemetryTracing(ctx, tc); err != nil { logrus.WithError(err).Error("unable to start OTLP trace exporter") } } } }) return err }