Add internal/tracing: env-gated OTel setup, span/log helpers, decorators

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-24 10:43:55 +02:00
parent b1a16774bf
commit 6b34f68469
12 changed files with 929 additions and 1 deletions

View File

@@ -0,0 +1,52 @@
package tracing
import (
"context"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
// NewReconciler wraps inner so every reconcile runs in a root span named
// "Reconcile <kind>" and every log line under it carries the trace context.
// Reconciles are triggered by watch events with no incoming trace to
// continue, so each one starts a new trace.
func NewReconciler(kind string, inner reconcile.Reconciler, opts ...Option) reconcile.Reconciler {
return &tracedReconciler{
spanName: "Reconcile " + kind,
inner: inner,
tracer: newOptions(opts).tracer(),
}
}
type tracedReconciler struct {
spanName string
inner reconcile.Reconciler
tracer trace.Tracer
}
func (t *tracedReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
ctx, span := StartSpan(ctx, t.tracer, t.spanName,
trace.WithAttributes(
attribute.String("k8s.namespace.name", req.Namespace),
attribute.String("k8s.object.name", req.Name),
))
defer span.End()
if id := controller.ReconcileIDFromContext(ctx); id != "" {
span.SetAttributes(attribute.String("reconcile.id", string(id)))
}
res, err := t.inner.Reconcile(ctx, req)
if err != nil {
span.SetStatus(codes.Error, err.Error())
return res, err
}
span.SetStatus(codes.Ok, "")
if res.RequeueAfter > 0 {
span.SetAttributes(attribute.Int64("reconcile.requeue_after_ms", res.RequeueAfter.Milliseconds()))
}
return res, nil
}