package provider import "context" // RequestRecorder receives one record per provider API call. Implemented // by internal/metrics; defined here so this package needs no metrics // dependency. type RequestRecorder interface { ProviderRequest(provider, op, result string) } // WithMetrics wraps a Provider so every call is recorded with its // classified result — zero-cost instrumentation for the next five // providers, and the one place Class is called purely for observability. func WithMetrics(name string, p Provider, rec RequestRecorder) Provider { return &instrumented{name: name, inner: p, rec: rec} } type instrumented struct { name string inner Provider rec RequestRecorder } func (i *instrumented) Create(ctx context.Context, req CreateRequest) (string, error) { id, err := i.inner.Create(ctx, req) i.record("create", err) return id, err } func (i *instrumented) Get(ctx context.Context, providerID string) (*Instance, error) { inst, err := i.inner.Get(ctx, providerID) i.record("get", err) return inst, err } func (i *instrumented) Delete(ctx context.Context, providerID string) error { err := i.inner.Delete(ctx, providerID) i.record("delete", err) return err } func (i *instrumented) ListByTag(ctx context.Context) ([]Instance, error) { instances, err := i.inner.ListByTag(ctx) i.record("list", err) return instances, err } func (i *instrumented) record(op string, err error) { i.rec.ProviderRequest(i.name, op, resultLabel(err)) } const ( resultOK = "ok" resultNotFound = "not_found" resultQuotaExceeded = "quota_exceeded" resultPermanent = "permanent" resultTransient = "transient" ) func resultLabel(err error) string { switch Class(err) { case nil: return resultOK case ErrNotFound: return resultNotFound case ErrQuotaExceeded: return resultQuotaExceeded case ErrPermanent: return resultPermanent default: return resultTransient } }