Add provider.WithTracing decorator mirroring WithMetrics
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
80
internal/provider/tracing.go
Normal file
80
internal/provider/tracing.go
Normal file
@@ -0,0 +1,80 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user