Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always record opentelemetry traceId and spanId when the context is available, even if the span is not sampled #3980

Open
richieyan opened this issue Dec 11, 2024 · 0 comments

Comments

@richieyan
Copy link

richieyan commented Dec 11, 2024

Problem Statement

In the case Using OpenTelemetry Without Any Java Agent, I want to record traceId and spanId to associate with log, but I found the OTEL span is ignored if the span not sampled.

In OpenTelemetry'sSdkSpanBuilder.startSpan, the SkdSpan will return if samplingDecision is DROP, and span will not trigger span.onStart which the SentrySpanProcessor is required.

// io.opentelemetry.sdk.trace.SdkSpanBuilder#startSpan
    TraceState samplingResultTraceState =
        samplingResult.getUpdatedTraceState(parentSpanContext.getTraceState());
    SpanContext spanContext =
        ImmutableSpanContext.create(
            traceId,
            spanId,
            isSampled(samplingDecision) ? TraceFlags.getSampled() : TraceFlags.getDefault(),
            samplingResultTraceState,
            /* remote= */ false,
            tracerSharedState.isIdGeneratorSafeToSkipIdValidation());

    if (!isRecording(samplingDecision)) {
      return Span.wrap(spanContext);
    }
    // ..... omits
    return SdkSpan.startSpan(
        // ... omits
        startEpochNanos);
// io.opentelemetry.sdk.trace.SdkSpan#startSpan
    // Call onStart here instead of calling in the constructor to make sure the span is completely
    // initialized.
    if (spanProcessor.isStartRequired()) {
      spanProcessor.onStart(parentContext, span);
    }

SentrySpanProcessor.onStart will add a span to spanStorage, if not, this.spanStorage.get(spanId) will be null.

// io.sentry.opentelemetry.OpenTelemetryLinkErrorEventProcessor#process
if (Instrumenter.OTEL.equals(instrumenter)) {
            Span otelSpan = Span.current();
            String traceId = otelSpan.getSpanContext().getTraceId();
            String spanId = otelSpan.getSpanContext().getSpanId();
            if (TraceId.isValid(traceId) && SpanId.isValid(spanId)) {
                ISpan sentrySpan = this.spanStorage.get(spanId);
if (sentrySpan != null) {
                    SpanContext sentrySpanSpanContext = sentrySpan.getSpanContext();
                    String operation = sentrySpanSpanContext.getOperation();
                    io.sentry.SpanId parentSpanId = sentrySpanSpanContext.getParentSpanId();
                    SpanContext spanContext = new SpanContext(new SentryId(traceId), new io.sentry.SpanId(spanId), operation, parentSpanId, (TracesSamplingDecision)null);
                    event.getContexts().setTrace(spanContext);
                } else {
                   // ... omit 
                }
            } 

Could we add SpanId and TraceId even if spanStorage.get(spanId) is null, to create a SpanContext by OTEL, so that we can record traceId from OTEL.

Solution Brainstorm

if (TraceId.isValid(traceId) && SpanId.isValid(spanId)) {
                ISpan sentrySpan = this.spanStorage.get(spanId);
                if (sentrySpan != null) {
                    SpanContext sentrySpanSpanContext = sentrySpan.getSpanContext();
                    String operation = sentrySpanSpanContext.getOperation();
                    io.sentry.SpanId parentSpanId = sentrySpanSpanContext.getParentSpanId();
                    SpanContext spanContext = new SpanContext(new SentryId(traceId), new io.sentry.SpanId(spanId), operation, parentSpanId, (TracesSamplingDecision) null);
                    event.getContexts().setTrace(spanContext);
                    this.hub.getOptions().getLogger().log(SentryLevel.DEBUG, "Linking Sentry event %s to span %s created via OpenTelemetry (trace %s).", new Object[]{event.getEventId(), spanId, traceId});
                } else {
                    // spanId and traceId by otelSpan
         if (otelSpan instanceof ReadableSpan) {
                        ReadableSpan readableSpan = (ReadableSpan) otelSpan;
                        io.sentry.SpanId parentSpanId = Optional.ofNullable(readableSpan.getParentSpanContext())
                                .map(io.opentelemetry.api.trace.SpanContext::getSpanId)
                                .map(io.sentry.SpanId::new)
                                .orElse(null);
                        String operation = readableSpan.getName();
                        SpanContext spanContext = new SpanContext(new SentryId(traceId),
                                new io.sentry.SpanId(spanId), operation, parentSpanId, (TracesSamplingDecision) null);
                        event.getContexts().setTrace(spanContext);
                    } else {
                        SpanContext spanContext = new SpanContext(new SentryId(traceId),
                                new io.sentry.SpanId(spanId), "", null, (TracesSamplingDecision) null);
                        event.getContexts().setTrace(spanContext);
                    }
                    }
                }```

_No response_
@getsantry getsantry bot moved this to Waiting for: Product Owner in GitHub Issues with 👀 3 Dec 11, 2024
@richieyan richieyan changed the title Always record the traceId and spanId when the context is available, even if the span is not sampled Always record opentelemetry traceId and spanId when the context is available, even if the span is not sampled Dec 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Waiting for: Product Owner
Development

No branches or pull requests

1 participant