Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public final class io/sentry/opentelemetry/SentrySpanProcessor : io/opentelemetr

public final class io/sentry/opentelemetry/SpanDescriptionExtractor {
public fun <init> ()V
public fun extractSpanInfo (Lio/opentelemetry/sdk/trace/data/SpanData;Lio/sentry/opentelemetry/IOtelSpanWrapper;)Lio/sentry/opentelemetry/OtelSpanInfo;
public fun extractSpanInfo (Lio/opentelemetry/sdk/trace/data/SpanData;Lio/sentry/opentelemetry/IOtelSpanWrapper;Lio/sentry/SentryOptions;)Lio/sentry/opentelemetry/OtelSpanInfo;
}

public final class io/sentry/opentelemetry/SpanNode {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.semconv.HttpAttributes;
import io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes;
import io.opentelemetry.semconv.incubating.ProcessIncubatingAttributes;
import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes;
import io.sentry.Baggage;
Expand Down Expand Up @@ -200,7 +201,7 @@ private void createAndFinishSpanForOtelSpan(
final @Nullable IOtelSpanWrapper sentrySpanMaybe =
spanStorage.getSentrySpan(spanData.getSpanContext());
final @NotNull OtelSpanInfo spanInfo =
spanDescriptionExtractor.extractSpanInfo(spanData, sentrySpanMaybe);
spanDescriptionExtractor.extractSpanInfo(spanData, sentrySpanMaybe, scopes.getOptions());

scopes
.getOptions()
Expand Down Expand Up @@ -294,7 +295,7 @@ private void transferSpanDetails(
final @NotNull IScopes scopesToUse =
scopesToUseBeforeForking.forkedCurrentScope("SentrySpanExporter.createTransaction");
final @NotNull OtelSpanInfo spanInfo =
spanDescriptionExtractor.extractSpanInfo(span, sentrySpanMaybe);
spanDescriptionExtractor.extractSpanInfo(span, sentrySpanMaybe, scopesToUse.getOptions());

scopesToUse
.getOptions()
Expand Down Expand Up @@ -361,6 +362,23 @@ private void transferSpanDetails(
maybeTransferOtelAttribute(span, sentryTransaction, ThreadIncubatingAttributes.THREAD_ID);
maybeTransferOtelAttribute(span, sentryTransaction, ThreadIncubatingAttributes.THREAD_NAME);

// Root transactions don't bulk-copy OTel attributes into span data (unlike child spans).
// The Sentry Queues product reads `trace.data.messaging.*`, so messaging attributes must
// be explicitly transferred for consumer root transactions to show up correctly. These are
// operational metadata (no payload contents) and are safe to transfer unconditionally.
maybeTransferOtelAttribute(
span, sentryTransaction, MessagingIncubatingAttributes.MESSAGING_SYSTEM);
maybeTransferOtelAttribute(
span, sentryTransaction, MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME);
maybeTransferOtelAttribute(
span, sentryTransaction, MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE);
maybeTransferOtelAttribute(
span, sentryTransaction, MessagingIncubatingAttributes.MESSAGING_MESSAGE_ID);
maybeTransferOtelAttribute(
span, sentryTransaction, MessagingIncubatingAttributes.MESSAGING_MESSAGE_BODY_SIZE);
maybeTransferOtelAttribute(
span, sentryTransaction, MessagingIncubatingAttributes.MESSAGING_MESSAGE_ENVELOPE_SIZE);

scopesToUse.configureScope(
ScopeType.CURRENT,
scope -> attributesExtractor.extract(span, scope, scopesToUse.getOptions()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ private boolean isSentryRequest(final @NotNull ReadableSpan otelSpan) {
private void updateTransactionWithOtelData(
final @NotNull ITransaction sentryTransaction, final @NotNull ReadableSpan otelSpan) {
final @NotNull OtelSpanInfo otelSpanInfo =
spanDescriptionExtractor.extractSpanInfo(otelSpan.toSpanData(), null);
spanDescriptionExtractor.extractSpanInfo(otelSpan.toSpanData(), null, scopes.getOptions());
sentryTransaction.setOperation(otelSpanInfo.getOp());
String transactionName = otelSpanInfo.getDescription();
sentryTransaction.setName(
Expand Down Expand Up @@ -334,7 +334,7 @@ private void updateSpanWithOtelData(
});

final @NotNull OtelSpanInfo otelSpanInfo =
spanDescriptionExtractor.extractSpanInfo(otelSpan.toSpanData(), null);
spanDescriptionExtractor.extractSpanInfo(otelSpan.toSpanData(), null, scopes.getOptions());
sentrySpan.setOperation(otelSpanInfo.getOp());
sentrySpan.setDescription(otelSpanInfo.getDescription());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import io.opentelemetry.semconv.UrlAttributes;
import io.opentelemetry.semconv.incubating.DbIncubatingAttributes;
import io.opentelemetry.semconv.incubating.HttpIncubatingAttributes;
import io.opentelemetry.semconv.incubating.MessagingIncubatingAttributes;
import io.sentry.SentryOptions;
import io.sentry.protocol.TransactionNameSource;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
Expand All @@ -17,9 +19,19 @@ public final class SpanDescriptionExtractor {

@SuppressWarnings("deprecation")
public @NotNull OtelSpanInfo extractSpanInfo(
final @NotNull SpanData otelSpan, final @Nullable IOtelSpanWrapper sentrySpan) {
final @NotNull SpanData otelSpan,
final @Nullable IOtelSpanWrapper sentrySpan,
final @NotNull SentryOptions options) {
final @NotNull Attributes attributes = otelSpan.getAttributes();

if (options.isEnableQueueTracing()) {
final @Nullable String messagingSystem =
attributes.get(MessagingIncubatingAttributes.MESSAGING_SYSTEM);
if (messagingSystem != null) {
return descriptionForMessagingSystem(otelSpan);
}
}

final @Nullable String httpMethod = attributes.get(HttpAttributes.HTTP_REQUEST_METHOD);
if (httpMethod != null) {
return descriptionForHttpMethod(otelSpan, httpMethod);
Expand Down Expand Up @@ -91,6 +103,57 @@ private static boolean isRootSpan(SpanData otelSpan) {
return !otelSpan.getParentSpanContext().isValid() || otelSpan.getParentSpanContext().isRemote();
}

@SuppressWarnings("deprecation")
private OtelSpanInfo descriptionForMessagingSystem(final @NotNull SpanData otelSpan) {
final @NotNull Attributes attributes = otelSpan.getAttributes();
final @NotNull String op = opForMessaging(otelSpan);
final @Nullable String destination =
attributes.get(MessagingIncubatingAttributes.MESSAGING_DESTINATION_NAME);
final @NotNull String description = destination != null ? destination : otelSpan.getName();
return new OtelSpanInfo(op, description, TransactionNameSource.TASK);
}

@SuppressWarnings("deprecation")
private @NotNull String opForMessaging(final @NotNull SpanData otelSpan) {
final @NotNull Attributes attributes = otelSpan.getAttributes();
// Prefer `messaging.operation.type` (current OTel semconv), fall back to legacy
// `messaging.operation`. OTel's SpanKind.CONSUMER is overloaded for both `receive` and
// `process`, so attribute-first mapping is required. SpanKind is used only as a last resort.
@Nullable
String operationType = attributes.get(MessagingIncubatingAttributes.MESSAGING_OPERATION_TYPE);
if (operationType == null) {
operationType = attributes.get(MessagingIncubatingAttributes.MESSAGING_OPERATION);
}
if (operationType != null) {
switch (operationType) {
case "publish":
case "send":
return "queue.publish";
case "create":
return "queue.create";
case "receive":
return "queue.receive";
case "process":
case "deliver":
return "queue.process";
case "settle":
return "queue.settle";
default:
// fall through to SpanKind mapping
break;
}
}

final @NotNull SpanKind kind = otelSpan.getKind();
if (SpanKind.PRODUCER.equals(kind)) {
return "queue.publish";
}
if (SpanKind.CONSUMER.equals(kind)) {
return "queue.process";
}
return "queue";
}

@SuppressWarnings("deprecation")
private OtelSpanInfo descriptionForDbSystem(final @NotNull SpanData otelSpan) {
final @NotNull Attributes attributes = otelSpan.getAttributes();
Expand Down
Loading
Loading