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

io.sentry.openfeign.SentryFeignClient Maven / Gradle / Ivy

There is a newer version: 8.0.0-alpha.4
Show newest version
package io.sentry.openfeign;

import static io.sentry.TypeCheckHint.OPEN_FEIGN_REQUEST;
import static io.sentry.TypeCheckHint.OPEN_FEIGN_RESPONSE;

import feign.Client;
import feign.Request;
import feign.Response;
import io.sentry.BaggageHeader;
import io.sentry.Breadcrumb;
import io.sentry.Hint;
import io.sentry.IHub;
import io.sentry.ISpan;
import io.sentry.SpanDataConvention;
import io.sentry.SpanStatus;
import io.sentry.util.Objects;
import io.sentry.util.TracingUtils;
import io.sentry.util.UrlUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/** A Feign client that creates a span around each executed HTTP call. */
public final class SentryFeignClient implements Client {
  private static final String TRACE_ORIGIN = "auto.http.openfeign";
  private final @NotNull Client delegate;
  private final @NotNull IHub hub;
  private final @Nullable BeforeSpanCallback beforeSpan;

  public SentryFeignClient(
      final @NotNull Client delegate,
      final @NotNull IHub hub,
      final @Nullable BeforeSpanCallback beforeSpan) {
    this.delegate = Objects.requireNonNull(delegate, "delegate is required");
    this.hub = Objects.requireNonNull(hub, "hub is required");
    this.beforeSpan = beforeSpan;
  }

  @Override
  public Response execute(final @NotNull Request request, final @NotNull Request.Options options)
      throws IOException {
    Response response = null;
    try {
      final ISpan activeSpan = hub.getSpan();

      if (activeSpan == null) {
        final @NotNull Request modifiedRequest = maybeAddTracingHeaders(request, null);
        return delegate.execute(modifiedRequest, options);
      }

      ISpan span = activeSpan.startChild("http.client");
      span.getSpanContext().setOrigin(TRACE_ORIGIN);
      final @NotNull UrlUtils.UrlDetails urlDetails = UrlUtils.parse(request.url());
      final @NotNull String method = request.httpMethod().name();
      span.setDescription(method + " " + urlDetails.getUrlOrFallback());
      span.setData(SpanDataConvention.HTTP_METHOD_KEY, method.toUpperCase(Locale.ROOT));
      urlDetails.applyToSpan(span);

      final @NotNull Request modifiedRequest = maybeAddTracingHeaders(request, span);

      try {
        response = delegate.execute(modifiedRequest, options);
        // handles both success and error responses
        span.setData(SpanDataConvention.HTTP_STATUS_CODE_KEY, response.status());
        span.setStatus(SpanStatus.fromHttpStatusCode(response.status()));
        return response;
      } catch (Throwable e) {
        // handles cases like connection errors
        span.setThrowable(e);
        span.setStatus(SpanStatus.INTERNAL_ERROR);
        throw e;
      } finally {
        if (beforeSpan != null) {
          final ISpan result = beforeSpan.execute(span, request, response);

          if (result == null) {
            // span is dropped
            span.getSpanContext().setSampled(false);
          } else {
            span.finish();
          }
        } else {
          span.finish();
        }
      }
    } finally {
      addBreadcrumb(request, response);
    }
  }

  private @NotNull Request maybeAddTracingHeaders(
      final @NotNull Request request, final @Nullable ISpan span) {
    final @NotNull RequestWrapper requestWrapper = new RequestWrapper(request);
    final @Nullable Collection requestBaggageHeaders =
        request.headers().get(BaggageHeader.BAGGAGE_HEADER);

    final @Nullable TracingUtils.TracingHeaders tracingHeaders =
        TracingUtils.traceIfAllowed(
            hub,
            request.url(),
            (requestBaggageHeaders != null ? new ArrayList<>(requestBaggageHeaders) : null),
            span);

    if (tracingHeaders != null) {
      requestWrapper.header(
          tracingHeaders.getSentryTraceHeader().getName(),
          tracingHeaders.getSentryTraceHeader().getValue());

      final @Nullable BaggageHeader baggageHeader = tracingHeaders.getBaggageHeader();
      if (baggageHeader != null) {
        requestWrapper.removeHeader(BaggageHeader.BAGGAGE_HEADER);
        requestWrapper.header(baggageHeader.getName(), baggageHeader.getValue());
      }
    }

    return requestWrapper.build();
  }

  private void addBreadcrumb(final @NotNull Request request, final @Nullable Response response) {
    final Breadcrumb breadcrumb =
        Breadcrumb.http(
            request.url(),
            request.httpMethod().name(),
            response != null ? response.status() : null);
    breadcrumb.setData("request_body_size", request.body() != null ? request.body().length : 0);
    if (response != null && response.body() != null && response.body().length() != null) {
      breadcrumb.setData("response_body_size", response.body().length());
    }

    final Hint hint = new Hint();
    hint.set(OPEN_FEIGN_REQUEST, request);
    if (response != null) {
      hint.set(OPEN_FEIGN_RESPONSE, response);
    }

    hub.addBreadcrumb(breadcrumb, hint);
  }

  static final class RequestWrapper {
    private final @NotNull Request delegate;

    private final @NotNull Map> headers;

    RequestWrapper(final @NotNull Request delegate) {
      this.delegate = delegate;
      this.headers = new LinkedHashMap<>(delegate.headers());
    }

    public void header(final @NotNull String name, final @NotNull String value) {
      if (!headers.containsKey(name)) {
        headers.put(name, Collections.singletonList(value));
      }
    }

    public void removeHeader(final @NotNull String name) {
      headers.remove(name);
    }

    @NotNull
    Request build() {
      return Request.create(
          delegate.httpMethod(),
          delegate.url(),
          headers,
          delegate.body(),
          delegate.charset(),
          delegate.requestTemplate());
    }
  }

  public interface BeforeSpanCallback {
    @Nullable
    ISpan execute(@NotNull ISpan span, @NotNull Request request, @Nullable Response response);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy