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 " 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 }