Skip to content

Commit

Permalink
Use OTel status for Sentry span API
Browse files Browse the repository at this point in the history
  • Loading branch information
adinauer committed May 23, 2024
1 parent 36ed84a commit 8f56e3f
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ public final class io/sentry/opentelemetry/OtelContextScopesStorage : io/sentry/
public fun set (Lio/sentry/IScopes;)Lio/sentry/ISentryLifecycleToken;
}

public final class io/sentry/opentelemetry/OtelSpanContext : io/sentry/SpanContext {
public fun <init> (Lio/opentelemetry/sdk/trace/ReadWriteSpan;Lio/opentelemetry/api/trace/Span;)V
public fun getStatus ()Lio/sentry/SpanStatus;
public fun setStatus (Lio/sentry/SpanStatus;)V
}

public final class io/sentry/opentelemetry/OtelSpanFactory : io/sentry/ISpanFactory {
public fun <init> ()V
public fun createSpan (Ljava/lang/String;Ljava/lang/String;Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/ISpan;)Lio/sentry/ISpan;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.sentry.opentelemetry;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.sdk.trace.ReadWriteSpan;
import io.opentelemetry.sdk.trace.data.StatusData;
import io.sentry.SpanContext;
import io.sentry.SpanId;
import io.sentry.SpanStatus;
import io.sentry.protocol.SentryId;
import java.lang.ref.WeakReference;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public final class OtelSpanContext extends SpanContext {

/**
* OpenTelemetry span which this wrapper wraps. Needs to be referenced weakly as otherwise we'd
* create a circular reference from {@link io.opentelemetry.sdk.trace.data.SpanData} to {@link
* OtelSpanWrapper} and indirectly back to {@link io.opentelemetry.sdk.trace.data.SpanData} via
* {@link Span}. Also see {@link SentryWeakSpanStorage}.
*/
private final @NotNull WeakReference<ReadWriteSpan> span;

public OtelSpanContext(final @NotNull ReadWriteSpan span, final @Nullable Span parentSpan) {
// TODO [POTEL] tracesSamplingDecision
super(
new SentryId(span.getSpanContext().getTraceId()),
new SpanId(span.getSpanContext().getSpanId()),
parentSpan == null ? null : new SpanId(parentSpan.getSpanContext().getSpanId()),
span.getName(),
null,
null,
null,
null);
this.span = new WeakReference<>(span);
}

@Override
public @Nullable SpanStatus getStatus() {
final @Nullable ReadWriteSpan otelSpan = span.get();

if (otelSpan != null) {
final @NotNull StatusData otelStatus = otelSpan.toSpanData().getStatus();
final @NotNull String otelStatusDescription = otelStatus.getDescription();
if (otelStatusDescription.isEmpty()) {
return otelStatusCodeFallback(otelStatus);
}
final @Nullable SpanStatus spanStatus = SpanStatus.fromApiNameSafely(otelStatusDescription);
if (spanStatus == null) {
return otelStatusCodeFallback(otelStatus);
}
return spanStatus;
}

return null;
}

@Override
public void setStatus(@Nullable SpanStatus status) {
if (status != null) {
final @Nullable ReadWriteSpan otelSpan = span.get();
if (otelSpan != null) {
final @NotNull StatusCode statusCode = translateStatusCode(status);
otelSpan.setStatus(statusCode, status.apiName());
}
}
}

private @Nullable SpanStatus otelStatusCodeFallback(final @NotNull StatusData otelStatus) {
if (otelStatus.getStatusCode() == StatusCode.ERROR) {
return SpanStatus.UNKNOWN_ERROR;
} else if (otelStatus.getStatusCode() == StatusCode.OK) {
return SpanStatus.OK;
}
return null;
}

private @NotNull StatusCode translateStatusCode(final @Nullable SpanStatus status) {
if (status == null) {
return StatusCode.UNSET;
} else if (status == SpanStatus.OK) {
return StatusCode.OK;
} else {
return StatusCode.ERROR;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,7 @@ public OtelSpanWrapper(
this.scopes = Objects.requireNonNull(scopes, "scopes are required");
this.span = new WeakReference<>(span);
this.startTimestamp = startTimestamp;
final @NotNull SentryId traceId = new SentryId(span.getSpanContext().getTraceId());
final @NotNull SpanId spanId = new SpanId(span.getSpanContext().getSpanId());
final @Nullable SpanId parentSpanId =
parentSpan == null ? null : new SpanId(parentSpan.getSpanContext().getSpanId());
@NotNull String operation = span.getName();

// TODO [POTEL] tracesSamplingDecision
this.context = new SpanContext(traceId, spanId, operation, parentSpanId, null);
this.context = new OtelSpanContext(span, parentSpan);
}

@Override
Expand Down Expand Up @@ -228,15 +221,12 @@ public void setDescription(@Nullable String description) {
}

@Override
public void setStatus(@Nullable SpanStatus status) {
// TODO [POTEL] need to find a way to transfer data from this wrapper to SpanExporter
// ^ could go in span attributes
this.context.setStatus(status);
public void setStatus(final @Nullable SpanStatus status) {
context.setStatus(status);
}

@Override
public @Nullable SpanStatus getStatus() {
// TODO [POTEL] need to find a way to transfer data from this wrapper to SpanExporter
return context.getStatus();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,8 @@ private List<SpanData> maybeSend(final @NotNull List<SpanData> spans) {

// spanStorage.getScope()
// transaction.finishWithScope
transaction.finish(mapOtelStatus(span), new SentryLongDate(span.getEndEpochNanos()));
transaction.finish(
mapOtelStatus(span, transaction), new SentryLongDate(span.getEndEpochNanos()));
}

return remaining.stream()
Expand Down Expand Up @@ -244,6 +245,9 @@ private void createAndFinishSpanForOtelSpan(
parentSentrySpan.startChild(
spanInfo.getOp(), spanInfo.getDescription(), startDate, Instrumenter.OTEL);

// TODO [POTEL] Check if we want to use `instrumentationScopeInfo.name` and append it to
// `auto.otel`
// TODO [POTEL] For spans manually created via Sentry API we should set manual, not auto.otel
sentryChildSpan.getSpanContext().setOrigin(TRACE_ORIGN);
for (Map.Entry<String, Object> dataField : spanInfo.getDataFields().entrySet()) {
sentryChildSpan.setData(dataField.getKey(), dataField.getValue());
Expand All @@ -256,7 +260,7 @@ private void createAndFinishSpanForOtelSpan(
}

sentryChildSpan.finish(
mapOtelStatus(spanData), new SentryLongDate(spanData.getEndEpochNanos()));
mapOtelStatus(spanData, sentryChildSpan), new SentryLongDate(spanData.getEndEpochNanos()));
}

private void transferSpanDetails(
Expand Down Expand Up @@ -285,6 +289,8 @@ private void transferSpanDetails(
for (Map.Entry<String, String> entry : tags.entrySet()) {
targetSpan.setTag(entry.getKey(), entry.getValue());
}

targetSpan.setStatus(sourceSpan.getStatus());
}
}

Expand Down Expand Up @@ -463,7 +469,14 @@ private void createOrUpdateSpanNodeAndRefs(
}

@SuppressWarnings("deprecation")
private SpanStatus mapOtelStatus(final @NotNull SpanData otelSpanData) {
private SpanStatus mapOtelStatus(
final @NotNull SpanData otelSpanData, final @NotNull ISpan sentrySpan) {
final @Nullable SpanStatus existingStatus = sentrySpan.getStatus();
// TODO [POTEL] do we want the unknown error check here?
if (existingStatus != null && existingStatus != SpanStatus.UNKNOWN_ERROR) {
return existingStatus;
}

final @NotNull StatusData otelStatus = otelSpanData.getStatus();
final @NotNull StatusCode otelStatusCode = otelStatus.getStatusCode();

Expand Down
2 changes: 2 additions & 0 deletions sentry/api/sentry.api
Original file line number Diff line number Diff line change
Expand Up @@ -3255,6 +3255,8 @@ public final class io/sentry/SpanStatus : java/lang/Enum, io/sentry/JsonSerializ
public static final field UNIMPLEMENTED Lio/sentry/SpanStatus;
public static final field UNKNOWN Lio/sentry/SpanStatus;
public static final field UNKNOWN_ERROR Lio/sentry/SpanStatus;
public fun apiName ()Ljava/lang/String;
public static fun fromApiNameSafely (Ljava/lang/String;)Lio/sentry/SpanStatus;
public static fun fromHttpStatusCode (I)Lio/sentry/SpanStatus;
public static fun fromHttpStatusCode (Ljava/lang/Integer;Lio/sentry/SpanStatus;)Lio/sentry/SpanStatus;
public fun serialize (Lio/sentry/ObjectWriter;Lio/sentry/ILogger;)V
Expand Down
9 changes: 5 additions & 4 deletions sentry/src/main/java/io/sentry/SpanContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public SpanId getParentSpanId() {
}

public @NotNull String getOperation() {
// TODO [POTEL] use span name here
return op;
}

Expand Down Expand Up @@ -223,12 +224,12 @@ public boolean equals(Object o) {
&& Objects.equals(parentSpanId, that.parentSpanId)
&& op.equals(that.op)
&& Objects.equals(description, that.description)
&& status == that.status;
&& getStatus() == that.getStatus();
}

@Override
public int hashCode() {
return Objects.hash(traceId, spanId, parentSpanId, op, description, status);
return Objects.hash(traceId, spanId, parentSpanId, op, description, getStatus());
}

// region JsonSerializable
Expand Down Expand Up @@ -260,8 +261,8 @@ public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger
if (description != null) {
writer.name(JsonKeys.DESCRIPTION).value(description);
}
if (status != null) {
writer.name(JsonKeys.STATUS).value(logger, status);
if (getStatus() != null) {
writer.name(JsonKeys.STATUS).value(logger, getStatus());
}
if (origin != null) {
writer.name(JsonKeys.ORIGIN).value(logger, origin);
Expand Down
17 changes: 16 additions & 1 deletion sentry/src/main/java/io/sentry/SpanStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,27 @@ private boolean matches(int httpStatusCode) {
return httpStatusCode >= minHttpStatusCode && httpStatusCode <= maxHttpStatusCode;
}

public @NotNull String apiName() {
return name().toLowerCase(Locale.ROOT);
}

public static @Nullable SpanStatus fromApiNameSafely(final @Nullable String apiName) {
if (apiName == null) {
return null;
}
try {
return SpanStatus.valueOf(apiName.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException ex) {
return null;
}
}

// JsonSerializable

@Override
public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger)
throws IOException {
writer.value(name().toLowerCase(Locale.ROOT));
writer.value(apiName());
}

public static final class Deserializer implements JsonDeserializer<SpanStatus> {
Expand Down

0 comments on commit 8f56e3f

Please sign in to comment.