All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zalando.riptide.opentelemetry.OpenTelemetryPlugin Maven / Gradle / Ivy

package org.zalando.riptide.opentelemetry;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Multimaps;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.context.propagation.TextMapPropagator;
import org.springframework.http.client.ClientHttpResponse;
import org.zalando.fauxpas.ThrowingBiConsumer;
import org.zalando.riptide.Attribute;
import org.zalando.riptide.Plugin;
import org.zalando.riptide.RequestArguments;
import org.zalando.riptide.RequestExecution;
import org.zalando.riptide.opentelemetry.span.CompositeSpanDecorator;
import org.zalando.riptide.opentelemetry.span.ErrorSpanDecorator;
import org.zalando.riptide.opentelemetry.span.HttpMethodSpanDecorator;
import org.zalando.riptide.opentelemetry.span.HttpPathSpanDecorator;
import org.zalando.riptide.opentelemetry.span.HttpStatusCodeSpanDecorator;
import org.zalando.riptide.opentelemetry.span.PeerHostSpanDecorator;
import org.zalando.riptide.opentelemetry.span.SpanDecorator;

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.BiConsumer;

public class OpenTelemetryPlugin implements Plugin {

    public static final Attribute OPERATION_NAME = Attribute.generate();

    private final OpenTelemetry telemetry;
    private final Tracer tracer;
    private final TextMapPropagator propagator;
    private final SpanDecorator spanDecorator;

    public OpenTelemetryPlugin(final SpanDecorator... decorators) {
        this(GlobalOpenTelemetry.get(), decorators);
    }

    public OpenTelemetryPlugin(final OpenTelemetry telemetry, final SpanDecorator... decorators) {
        this(telemetry, CompositeSpanDecorator.composite(
                new HttpMethodSpanDecorator(),
                new HttpStatusCodeSpanDecorator(),
                new ErrorSpanDecorator(),
                new PeerHostSpanDecorator(),
                new HttpPathSpanDecorator(),
                CompositeSpanDecorator.composite(decorators)
        ));
    }

    private OpenTelemetryPlugin(final OpenTelemetry telemetry, final SpanDecorator spanDecorator) {
        this.telemetry = telemetry;
        this.tracer = telemetry.getTracer("riptide-opentelemetry");
        this.propagator = telemetry.getPropagators().getTextMapPropagator();
        this.spanDecorator = spanDecorator;
    }

    public OpenTelemetryPlugin withSpanDecorators(final SpanDecorator... decorators) {
        return new OpenTelemetryPlugin(telemetry, CompositeSpanDecorator.composite(decorators));
    }

    @Override
    public RequestExecution aroundAsync(@Nonnull final RequestExecution execution) {
        final Context context = Context.current();
        return arguments -> trace(context, execution, arguments);
    }

    private CompletableFuture trace(
            final Context context,
            final RequestExecution execution,
            final RequestArguments arguments) throws IOException {

        final String operationName = arguments.getAttribute(OPERATION_NAME)
                                              .orElse(arguments.getMethod().name());
        final Span span = tracer.spanBuilder(operationName)
                                .setSpanKind(SpanKind.CLIENT)
                                .setParent(context)
                                .startSpan();

        spanDecorator.onRequest(span, arguments);

        final Map headers = new HashMap<>();
        try (final Scope ignored = context.with(span).makeCurrent()) {
            this.propagator.inject(Context.current(), headers, Map::put);

            return execution.execute(arguments.withHeaders(Multimaps.forMap(headers).asMap()))
                            .whenComplete(decorateOnResponse(span, arguments))
                            .whenComplete(decorateOnError(span, arguments))
                            .whenComplete((r, t) -> span.end());
        }
    }

    private ThrowingBiConsumer decorateOnResponse(
            final Span span,
            final RequestArguments arguments) {

        return (response, error) -> {
            if (response != null) {
                spanDecorator.onResponse(span, arguments, response);
            }
        };
    }

    private BiConsumer decorateOnError(
            final Span span,
            final RequestArguments arguments) {

        return (response, error) -> {
            if (error != null) {
                spanDecorator.onError(span, arguments, unpack(error));
            }
        };
    }

    @VisibleForTesting
    static Throwable unpack(final Throwable error) {
        return error instanceof CompletionException ? error.getCause() : error;
    }

    @VisibleForTesting
    Tracer getTracer() {
        return tracer;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy