diff --git a/registry/registry.go b/registry/registry.go index 54869c68..6fb79f18 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -17,6 +17,7 @@ import ( gorhandlers "github.com/gorilla/handlers" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" "golang.org/x/crypto/acme" "golang.org/x/crypto/acme/autocert" @@ -25,6 +26,7 @@ import ( "github.com/distribution/distribution/v3/internal/dcontext" "github.com/distribution/distribution/v3/registry/handlers" "github.com/distribution/distribution/v3/registry/listener" + "github.com/distribution/distribution/v3/tracing" "github.com/distribution/distribution/v3/version" ) @@ -152,6 +154,12 @@ func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Reg handler = applyHandlerMiddleware(config, handler) } + err = tracing.InitOpenTelemetry(app.Context) + if err != nil { + return nil, fmt.Errorf("error during open telemetry initialization: %v", err) + } + handler = otelHandler(handler) + server := &http.Server{ Handler: handler, } @@ -163,6 +171,13 @@ func NewRegistry(ctx context.Context, config *configuration.Configuration) (*Reg }, nil } +// otelHandler returns an http.Handler that wraps the provided `next` handler with OpenTelemetry instrumentation. +// This instrumentation tracks each HTTP request, creating spans with names derived from the request method and URL path. +func otelHandler(next http.Handler) http.Handler { + return otelhttp.NewHandler(next, "", + otelhttp.WithSpanNameFormatter(func(_ string, r *http.Request) string { return r.Method + " " + r.URL.Path })) +} + // takes a list of cipher suites and converts it to a list of respective tls constants // if an empty list is provided, then the defaults will be used func getCipherSuites(names []string) ([]uint16, error) { diff --git a/tracing/tracing.go b/tracing/tracing.go new file mode 100644 index 00000000..aa304dec --- /dev/null +++ b/tracing/tracing.go @@ -0,0 +1,49 @@ +package tracing + +import ( + "context" + + "github.com/distribution/distribution/v3/version" + "go.opentelemetry.io/contrib/exporters/autoexport" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/propagation" + "go.opentelemetry.io/otel/sdk/resource" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.4.0" +) + +const ( + // ServiceName is trace service name + serviceName = "distribution" + + // DefaultSamplingRatio default sample ratio + defaultSamplingRatio = 1 +) + +// InitOpenTelemetry initializes OpenTelemetry for the application. This function sets up the +// necessary components for collecting telemetry data, such as traces. +func InitOpenTelemetry(ctx context.Context) error { + res := resource.NewWithAttributes( + semconv.SchemaURL, + semconv.ServiceNameKey.String(serviceName), + semconv.ServiceVersionKey.String(version.Version), + ) + + exp, err := autoexport.NewSpanExporter(ctx) + if err != nil { + return err + } + + sp := sdktrace.NewBatchSpanProcessor(exp) + provider := sdktrace.NewTracerProvider( + sdktrace.WithSampler(sdktrace.TraceIDRatioBased(defaultSamplingRatio)), + sdktrace.WithResource(res), + sdktrace.WithSpanProcessor(sp), + ) + otel.SetTracerProvider(provider) + + pr := propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}) + otel.SetTextMapPropagator(pr) + + return nil +} \ No newline at end of file