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

io.sentry.opentelemetry.SentrySampler Maven / Gradle / Ivy

There is a newer version: 8.0.0-rc.3
Show newest version
package io.sentry.opentelemetry;

import static io.sentry.opentelemetry.OtelInternalSpanDetectionUtil.isSentryRequest;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingDecision;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import io.sentry.Baggage;
import io.sentry.DataCategory;
import io.sentry.IScopes;
import io.sentry.PropagationContext;
import io.sentry.SamplingContext;
import io.sentry.ScopesAdapter;
import io.sentry.SentryLevel;
import io.sentry.SentryTraceHeader;
import io.sentry.SpanId;
import io.sentry.TracesSamplingDecision;
import io.sentry.TransactionContext;
import io.sentry.clientreport.DiscardReason;
import io.sentry.protocol.SentryId;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class SentrySampler implements Sampler {

  private final @NotNull SentryWeakSpanStorage spanStorage = SentryWeakSpanStorage.getInstance();
  private final @NotNull IScopes scopes;

  public SentrySampler(final @NotNull IScopes scopes) {
    this.scopes = scopes;
  }

  public SentrySampler() {
    this(ScopesAdapter.getInstance());
  }

  @Override
  public SamplingResult shouldSample(
      final @NotNull Context parentContext,
      final @NotNull String traceId,
      final @NotNull String name,
      final @NotNull SpanKind spanKind,
      final @NotNull Attributes attributes,
      final @NotNull List parentLinks) {
    if (isSentryRequest(scopes, spanKind, attributes)) {
      return SamplingResult.drop();
    }
    // note: parentLinks seems to usually be empty
    final @Nullable Span parentOtelSpan = Span.fromContextOrNull(parentContext);
    final @Nullable IOtelSpanWrapper parentSentrySpan =
        parentOtelSpan != null ? spanStorage.getSentrySpan(parentOtelSpan.getSpanContext()) : null;

    if (parentSentrySpan != null) {
      return copyParentSentryDecision(parentSentrySpan);
    } else {
      final @Nullable TracesSamplingDecision samplingDecision =
          OtelSamplingUtil.extractSamplingDecision(attributes);
      if (samplingDecision != null) {
        return new SentrySamplingResult(samplingDecision);
      } else {
        return handleRootOtelSpan(traceId, parentContext);
      }
    }
  }

  private @NotNull SamplingResult handleRootOtelSpan(
      final @NotNull String traceId, final @NotNull Context parentContext) {
    if (!scopes.getOptions().isTracingEnabled()) {
      return SamplingResult.create(SamplingDecision.RECORD_ONLY);
    }
    @Nullable Baggage baggage = null;
    @Nullable
    SentryTraceHeader sentryTraceHeader = parentContext.get(SentryOtelKeys.SENTRY_TRACE_KEY);
    @Nullable Baggage baggageFromContext = parentContext.get(SentryOtelKeys.SENTRY_BAGGAGE_KEY);
    if (sentryTraceHeader != null) {
      baggage = baggageFromContext;
    }

    // there's no way to get the span id here, so we just use a random id for sampling
    SpanId randomSpanId = new SpanId();
    final @NotNull PropagationContext propagationContext =
        sentryTraceHeader == null
            ? new PropagationContext(new SentryId(traceId), randomSpanId, null, baggage, null)
            : PropagationContext.fromHeaders(sentryTraceHeader, baggage, randomSpanId);

    final @NotNull TransactionContext transactionContext =
        TransactionContext.fromPropagationContext(propagationContext);
    final @NotNull TracesSamplingDecision sentryDecision =
        scopes
            .getOptions()
            .getInternalTracesSampler()
            .sample(new SamplingContext(transactionContext, null));

    if (!sentryDecision.getSampled()) {
      scopes
          .getOptions()
          .getClientReportRecorder()
          .recordLostEvent(DiscardReason.SAMPLE_RATE, DataCategory.Transaction);
      scopes
          .getOptions()
          .getClientReportRecorder()
          .recordLostEvent(DiscardReason.SAMPLE_RATE, DataCategory.Span);
    }

    return new SentrySamplingResult(sentryDecision);
  }

  private @NotNull SentrySamplingResult copyParentSentryDecision(
      final @NotNull IOtelSpanWrapper parentSentrySpan) {
    final @Nullable TracesSamplingDecision parentSamplingDecision =
        parentSentrySpan.getSamplingDecision();
    if (parentSamplingDecision != null) {
      if (!parentSamplingDecision.getSampled()) {
        scopes
            .getOptions()
            .getClientReportRecorder()
            .recordLostEvent(DiscardReason.SAMPLE_RATE, DataCategory.Span);
      }
      return new SentrySamplingResult(parentSamplingDecision);
    } else {
      scopes
          .getOptions()
          .getLogger()
          .log(
              SentryLevel.WARNING,
              "Encountered a missing parent sampling decision where one was expected.");
      return new SentrySamplingResult(new TracesSamplingDecision(true));
    }
  }

  @Override
  public String getDescription() {
    return "SentrySampler";
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy