package provider import ( "context" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/tracing" ) // WithTracing wraps a Provider so every call runs in a client span carrying // the classified result, mirroring WithMetrics. It goes through // tracing.StartSpan, so provider-internal logs (e.g. the gcp provider's // context logger) inherit traceID/spanID. Errors pass through unmodified. func WithTracing(name string, p Provider, opts ...tracing.Option) Provider { return &traced{name: name, inner: p, tracer: tracing.Tracer(opts...)} } type traced struct { name string inner Provider tracer trace.Tracer } func (t *traced) Create(ctx context.Context, req CreateRequest) (string, error) { ctx, span := t.start(ctx, "provider.create") id, err := t.inner.Create(ctx, req) if id != "" { span.SetAttributes(attribute.String("provider.id", id)) } t.end(span, err) return id, err } func (t *traced) Get(ctx context.Context, providerID string) (*Instance, error) { ctx, span := t.start(ctx, "provider.get") span.SetAttributes(attribute.String("provider.id", providerID)) inst, err := t.inner.Get(ctx, providerID) t.end(span, err) return inst, err } func (t *traced) Delete(ctx context.Context, providerID string) error { ctx, span := t.start(ctx, "provider.delete") span.SetAttributes(attribute.String("provider.id", providerID)) err := t.inner.Delete(ctx, providerID) t.end(span, err) return err } func (t *traced) ListByTag(ctx context.Context) ([]Instance, error) { ctx, span := t.start(ctx, "provider.list") instances, err := t.inner.ListByTag(ctx) span.SetAttributes(attribute.Int("provider.instances", len(instances))) t.end(span, err) return instances, err } func (t *traced) start(ctx context.Context, op string) (context.Context, trace.Span) { return tracing.StartSpan(ctx, t.tracer, op, trace.WithSpanKind(trace.SpanKindClient), trace.WithAttributes(attribute.String("provider.name", t.name))) } // end records the taxonomy class and closes the span. ErrNotFound stays Ok: // the reconciler polls Get to NotFound during replacement/deletion, so it is // an expected answer, not a failure — same reasoning as resultLabel's // distinct "not_found" bucket. func (t *traced) end(span trace.Span, err error) { span.SetAttributes(attribute.String("provider.result", resultLabel(err))) switch Class(err) { case nil, ErrNotFound: span.SetStatus(codes.Ok, "") default: span.SetStatus(codes.Error, err.Error()) } span.End() }