MessagingTracing.java

package com.ecommerce.platform.common.observability;

import io.micrometer.tracing.Span;
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.propagation.Propagator;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Keeps messaging instrumentation on the Micrometer Tracing API so the
 * OpenTelemetry SDK and the trace backend remain replaceable infrastructure.
 */
public final class MessagingTracing {

    private final Tracer tracer;
    private final Propagator propagator;

    public MessagingTracing(Tracer tracer, Propagator propagator) {
        this.tracer = tracer;
        this.propagator = propagator;
    }

    public Map<String, String> capture() {
        Span current = tracer.currentSpan();
        if (current == null) {
            return Map.of();
        }
        Map<String, String> carrier = new LinkedHashMap<>();
        propagator.inject(current.context(), carrier, Map::put);
        return Map.copyOf(carrier);
    }

    public String currentTraceId() {
        Span current = tracer.currentSpan();
        return current == null ? null : current.context().traceId();
    }

    public void inSpan(
            String name,
            Span.Kind kind,
            Map<String, String> parentContext,
            Map<String, String> tags,
            CheckedRunnable action) throws Exception {
        Span.Builder builder = parentContext == null || parentContext.isEmpty()
                ? tracer.spanBuilder()
                : propagator.extract(parentContext, Map::get);
        builder.name(name).kind(kind);
        tags.forEach(builder::tag);
        Span span = builder.start();
        try (Tracer.SpanInScope ignored = tracer.withSpan(span)) {
            action.run();
        } catch (Exception exception) {
            span.error(exception);
            throw exception;
        } finally {
            span.end();
        }
    }

    @FunctionalInterface
    public interface CheckedRunnable {
        void run() throws Exception;
    }
}