From 3a3ff125eda610b721d06880a665f237f6092a58 Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Mon, 14 Oct 2024 09:07:09 +0200 Subject: [PATCH 1/5] use SematicAttributes.HTTP_METHOD as fallback if HttpAttributes.HTTP_REQUEST_METHOD is null, add OtelSentrySpanProcessorTest --- .../SpanDescriptionExtractor.java | 17 +- .../kotlin/OtelSentrySpanProcessorTest.kt | 421 ++++++++++++++++++ .../sentry/opentelemetry/OtelSpanContext.java | 13 + 3 files changed, 441 insertions(+), 10 deletions(-) create mode 100644 sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java index 2a67ca46ad..462cbdab8e 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java @@ -17,19 +17,16 @@ public final class SpanDescriptionExtractor { @SuppressWarnings("deprecation") public @NotNull OtelSpanInfo extractSpanInfo( - final @NotNull SpanData otelSpan, final @Nullable OtelSpanWrapper sentrySpan) { + final @NotNull SpanData otelSpan, final @Nullable OtelSpanWrapper sentrySpan) { final @NotNull Attributes attributes = otelSpan.getAttributes(); - final @Nullable String httpMethod = attributes.get(HttpAttributes.HTTP_REQUEST_METHOD); + final @Nullable String httpMethod = attributes.get(HttpAttributes.HTTP_REQUEST_METHOD) != null + ? attributes.get(HttpAttributes.HTTP_REQUEST_METHOD) + : attributes.get(io.opentelemetry.semconv.SemanticAttributes.HTTP_METHOD); if (httpMethod != null) { return descriptionForHttpMethod(otelSpan, httpMethod); } - final @Nullable String httpRequestMethod = attributes.get(HttpAttributes.HTTP_REQUEST_METHOD); - if (httpRequestMethod != null) { - return descriptionForHttpMethod(otelSpan, httpRequestMethod); - } - final @Nullable String dbSystem = attributes.get(DbIncubatingAttributes.DB_SYSTEM); if (dbSystem != null) { return descriptionForDbSystem(otelSpan); @@ -37,14 +34,14 @@ public final class SpanDescriptionExtractor { final @NotNull String name = otelSpan.getName(); final @Nullable String maybeDescription = - sentrySpan != null ? sentrySpan.getDescription() : name; + sentrySpan != null ? sentrySpan.getDescription() : name; final @NotNull String description = maybeDescription != null ? maybeDescription : name; return new OtelSpanInfo(name, description, TransactionNameSource.CUSTOM); } @SuppressWarnings("deprecation") private OtelSpanInfo descriptionForHttpMethod( - final @NotNull SpanData otelSpan, final @NotNull String httpMethod) { + final @NotNull SpanData otelSpan, final @NotNull String httpMethod) { final @NotNull String name = otelSpan.getName(); final @NotNull SpanKind kind = otelSpan.getKind(); final @NotNull StringBuilder opBuilder = new StringBuilder("http"); @@ -75,7 +72,7 @@ private OtelSpanInfo descriptionForHttpMethod( final @NotNull String description = httpMethod + " " + httpPath; final @NotNull TransactionNameSource transactionNameSource = - httpRoute != null ? TransactionNameSource.ROUTE : TransactionNameSource.URL; + httpRoute != null ? TransactionNameSource.ROUTE : TransactionNameSource.URL; return new OtelSpanInfo(op, description, transactionNameSource); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt new file mode 100644 index 0000000000..ae04ed9ca2 --- /dev/null +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt @@ -0,0 +1,421 @@ +package io.sentry.opentelemetry + +import io.opentelemetry.api.OpenTelemetry +import io.opentelemetry.api.trace.* +import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.SpanContext +import io.opentelemetry.api.trace.SpanId +import io.opentelemetry.context.Context +import io.opentelemetry.context.propagation.ContextPropagators +import io.opentelemetry.context.propagation.TextMapGetter +import io.opentelemetry.context.propagation.TextMapSetter +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.trace.ReadWriteSpan +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor +import io.opentelemetry.semconv.HttpAttributes +import io.opentelemetry.semconv.UrlAttributes +import io.sentry.* +import io.sentry.util.SpanUtils +import org.mockito.kotlin.* +import java.net.http.HttpHeaders +import kotlin.test.* + +class OtelSentrySpanProcessorTest { + + companion object { + val SENTRY_TRACE_ID = "2722d9f6ec019ade60c776169d9a8904" + val SENTRY_TRACE_HEADER_STRING = "${SENTRY_TRACE_ID}-cedf5b7571cb4972-1" + val BAGGAGE_HEADER_STRING = "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET" + } + + private class Fixture { + + val options = SentryOptions().also { + it.dsn = "https://key@sentry.io/proj" + it.ignoredSpanOrigins = SpanUtils.ignoredSpanOriginsForOpenTelemetry() + it.spanFactory = OtelSpanFactory() + it.enableTracing = true + it.sampleRate = 1.0 + } + val scopes = mock() + lateinit var openTelemetry: OpenTelemetry + lateinit var tracer: Tracer + + fun setup() { + whenever(scopes.isEnabled).thenReturn(true) + whenever(scopes.options).thenReturn(options) + + val sdkTracerProvider = SdkTracerProvider.builder() + .setSampler(SentrySampler(scopes)) + .addSpanProcessor(OtelSentrySpanProcessor(scopes)) + .addSpanProcessor(BatchSpanProcessor.builder(SentrySpanExporter()).build()) + .build() + + openTelemetry = OpenTelemetrySdk.builder() + .setTracerProvider(sdkTracerProvider) + .setPropagators(ContextPropagators.create(OtelSentryPropagator())) + .build() + + + tracer = openTelemetry.getTracer("sentry-test") + } + } + + private val fixture = Fixture() + + @Test + fun `requires start`() { + val processor = OtelSentrySpanProcessor() + assertTrue(processor.isStartRequired) + } + + @Test + fun `requires end`() { + val processor = OtelSentrySpanProcessor() + assertTrue(processor.isEndRequired) + } + + @Test + fun `ignores sentry client request`() { + fixture.setup() + val otelSpan = givenSpanBuilder(SpanKind.CLIENT) + .setAttribute(UrlAttributes.URL_FULL, "https://key@sentry.io/proj/some-api") + .startSpan() + + thenNoSpanIsCreated(otelSpan) + } + + @Test + fun `ignores sentry internal request`() { + fixture.setup() + val otelSpan = givenSpanBuilder(SpanKind.CLIENT) + .setAttribute(UrlAttributes.URL_FULL, "https://key@sentry.io/proj/some-api") + .startSpan() + + thenNoSpanIsCreated(otelSpan) + } + + @Test + fun `does nothing on start if Sentry has not been initialized`() { + fixture.setup() + val context = mock() + val span = mock() + + whenever(fixture.scopes.isEnabled).thenReturn(false) + + OtelSentrySpanProcessor(fixture.scopes).onStart(context, span) + + verify(fixture.scopes).isEnabled + verify(fixture.scopes).options + verifyNoMoreInteractions(fixture.scopes) + verifyNoInteractions(context, span) + } + + @Test + fun `does not start transaction for invalid SpanId`() { + fixture.setup() + val mockSpan = mock() + val mockSpanContext = mock() + whenever(mockSpanContext.spanId).thenReturn(SpanId.getInvalid()) + whenever(mockSpan.spanContext).thenReturn(mockSpanContext) + OtelSentrySpanProcessor(fixture.scopes).onStart(Context.current(), mockSpan) + thenNoSpanIsCreated(mockSpan) + } + + @Test + fun `does not start transaction for invalid TraceId`() { + fixture.setup() + val mockSpan = mock() + val mockSpanContext = mock() + whenever(mockSpanContext.spanId).thenReturn(SpanId.fromBytes("seed".toByteArray())) + whenever(mockSpanContext.traceId).thenReturn(TraceId.getInvalid()) + whenever(mockSpan.spanContext).thenReturn(mockSpanContext) + OtelSentrySpanProcessor(fixture.scopes).onStart(Context.current(), mockSpan) + thenNoSpanIsCreated(mockSpan) + } + + @Test + fun `creates transaction for first otel span and span for second`() { + fixture.setup() + val otelSpan = givenSpanBuilder().startSpan() + thenSentrySpanIsCreated(otelSpan, isContinued = false) + + val otelChildSpan = givenSpanBuilder(SpanKind.CLIENT, parentSpan = otelSpan) + .startSpan() + thenChildSpanIsCreated(otelSpan, otelChildSpan) + + endSpanWithStatus(otelChildSpan) + thenSpanIsFinished(otelChildSpan) + + endSpanWithStatus(otelSpan) + thenSpanIsFinished(otelSpan) + } + + private fun whenExtractingHeaders(sentryTrace: Boolean = true, baggage: Boolean = true): Context { + val headers = givenHeaders(sentryTrace, baggage) + return fixture.openTelemetry.propagators.textMapPropagator.extract(Context.current(), headers, OtelHeaderGetter()) + } + + @Test + fun `propagator can extract and result is used for transaction and attached on inject`() { + fixture.setup() + val extractedContext = whenExtractingHeaders() + + extractedContext.makeCurrent().use { _ -> + val otelSpan = givenSpanBuilder().startSpan() + thenTraceIdIsUsed(otelSpan) + thenSentrySpanIsCreated(otelSpan, isContinued = true) + + val otelChildSpan = givenSpanBuilder(SpanKind.CLIENT, parentSpan = otelSpan) + .startSpan() + thenChildSpanIsCreated(otelSpan, otelChildSpan) + + val map = mutableMapOf() + fixture.openTelemetry.propagators.textMapPropagator.inject(Context.current().with(otelSpan), map, OtelTestSetter()) + + assertTrue(map.isNotEmpty()) + + assertEquals("${SENTRY_TRACE_ID}-${otelSpan.spanContext.spanId}-1", map["sentry-trace"]) + assertEquals(BAGGAGE_HEADER_STRING, map["baggage"]) + + endSpanWithStatus(otelChildSpan) + thenSpanIsFinished(otelChildSpan) + + endSpanWithStatus(otelSpan) + thenSpanIsFinished(otelSpan) + } + } + + @Test + fun `incoming baggage without sentry-trace is ignored`() { + fixture.setup() + val extractedContext = whenExtractingHeaders(sentryTrace = false, baggage = true) + + extractedContext.makeCurrent().use { _ -> + val otelSpan = givenSpanBuilder() + .startSpan() + thenTraceIdIsNotUsed(otelSpan) + thenSentrySpanIsCreated(otelSpan, isContinued = false) + + val otelChildSpan = givenSpanBuilder(SpanKind.CLIENT, parentSpan = otelSpan) + .startSpan() + thenChildSpanIsCreated(otelSpan, otelChildSpan) + + endSpanWithStatus(otelChildSpan) + thenSpanIsFinished(otelChildSpan) + + endSpanWithStatus(otelSpan) + thenSpanIsFinished(otelSpan) + } + } + + @Test + fun `sentry-trace without baggage continues trace`() { + fixture.setup() + val extractedContext = whenExtractingHeaders(sentryTrace = true, baggage = false) + + extractedContext.makeCurrent().use { _ -> + val otelSpan = givenSpanBuilder() + .startSpan() + + thenTraceIdIsUsed(otelSpan) + thenSentrySpanIsCreated(otelSpan, isContinued = true, continuesWithFilledBaggage = false) + + val otelChildSpan = givenSpanBuilder(SpanKind.CLIENT, parentSpan = otelSpan) + .startSpan() + thenChildSpanIsCreated(otelSpan, otelChildSpan) + + endSpanWithStatus(otelChildSpan) + thenSpanIsFinished(otelChildSpan) + + endSpanWithStatus(otelSpan) + thenSpanIsFinished(otelSpan) + } + } + + @Test + fun `sets status for errored span`() { + fixture.setup() + val otelSpan = givenSpanBuilder().startSpan() + thenSentrySpanIsCreated(otelSpan, isContinued = false) + + val otelChildSpan = givenSpanBuilder(SpanKind.CLIENT, parentSpan = otelSpan) + .startSpan() + thenChildSpanIsCreated(otelSpan, otelChildSpan) + + otelChildSpan.setStatus(StatusCode.ERROR, "NOT_FOUND") + otelChildSpan.setAttribute(UrlAttributes.URL_FULL, "http://github.com/getsentry/sentry-java") + otelChildSpan.setAttribute(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, 404L) + + otelChildSpan.end() + thenSpanIsFinished(otelChildSpan, SpanStatus.NOT_FOUND) + + endSpanWithStatus(otelSpan, StatusCode.OK) + thenSpanIsFinished(otelSpan) + } + + @Test + fun `sets status for errored span if not http`() { + fixture.setup() + val otelSpan = givenSpanBuilder().startSpan() + thenSentrySpanIsCreated(otelSpan, isContinued = false) + + val otelChildSpan = givenSpanBuilder(SpanKind.CLIENT, parentSpan = otelSpan) + .startSpan() + thenChildSpanIsCreated(otelSpan, otelChildSpan) + + otelChildSpan.setStatus(StatusCode.ERROR) + + otelChildSpan.end() + thenSpanIsFinished(otelChildSpan, SpanStatus.UNKNOWN_ERROR) + + endSpanWithStatus(otelSpan, StatusCode.OK) + thenSpanIsFinished(otelSpan) + } + + @Test + @Ignore + //TODO [POTEL] + fun `links error to OTEL transaction`() { + fixture.setup() + val extractedContext = whenExtractingHeaders() + + extractedContext.makeCurrent().use { _ -> + val otelSpan = givenSpanBuilder().startSpan() + thenSentrySpanIsCreated(otelSpan, isContinued = true) + + otelSpan.makeCurrent().use { _ -> + val processedEvent = OpenTelemetryLinkErrorEventProcessor(fixture.scopes).process(SentryEvent(), Hint()) + val traceContext = processedEvent!!.contexts.trace!! + + assertEquals(SENTRY_TRACE_ID, traceContext.traceId.toString()) + assertEquals(otelSpan.spanContext.spanId, traceContext.spanId.toString()) + assertEquals("cedf5b7571cb4972", traceContext.parentSpanId.toString()) + assertEquals("spanContextOp", traceContext.operation) + } + + otelSpan.end() +// thenTransactionIsFinished() + } + } + + private fun givenSpanBuilder(spanKind: SpanKind = SpanKind.SERVER, parentSpan: Span? = null): SpanBuilder { + val spanName = if (parentSpan == null) "testspan" else "childspan" + val spanBuilder = fixture.tracer + .spanBuilder(spanName) + .setAttribute("some-attribute", "some-value") + .setSpanKind(spanKind) + + parentSpan?.let { spanBuilder.setParent(Context.current().with(parentSpan)) } + + return spanBuilder + } + + private fun givenHeaders(sentryTrace: Boolean = true, baggage: Boolean = true): HttpHeaders? { + val headerMap = mutableMapOf>().also { + if (sentryTrace) { + it.put("sentry-trace", listOf(SENTRY_TRACE_HEADER_STRING)) + } + if (baggage) { + it.put("baggage", listOf(BAGGAGE_HEADER_STRING)) + } + } + + return HttpHeaders.of(headerMap) { _, _ -> true } + } + + private fun endSpanWithStatus(span: Span, status: StatusCode = StatusCode.OK) { + span.setStatus(status) + span.end() + } + + private fun thenSentrySpanIsCreated(otelSpan: Span, isContinued: Boolean = false, continuesWithFilledBaggage: Boolean = true) { + val sentrySpan = SentryWeakSpanStorage.getInstance().getSentrySpan(otelSpan.spanContext) + assertNotNull(sentrySpan) + val spanContext = sentrySpan.spanContext + + if (isContinued) { + assertNull(spanContext.description) +// assertEquals(TransactionNameSource.CUSTOM, spanContext.transactionNameSource) + assertEquals("testspan", spanContext.operation) + assertEquals(otelSpan.spanContext.spanId, spanContext.spanId.toString()) + assertEquals(SENTRY_TRACE_ID, spanContext.traceId.toString()) + assertEquals("cedf5b7571cb4972", spanContext.parentSpanId?.toString()) + assertEquals(true, spanContext.sampled) + + if (continuesWithFilledBaggage) { + val baggage = spanContext.baggage + assertNotNull(baggage) + assertEquals(SENTRY_TRACE_ID, baggage.traceId) + assertEquals("1", baggage.sampleRate) + assertEquals("HTTP GET", baggage.transaction) + assertEquals("502f25099c204a2fbf4cb16edc5975d1", baggage.publicKey) + assertFalse(baggage.isMutable) + } else { + assertNotNull(spanContext.baggage) + assertNull(spanContext.baggage?.traceId) + assertNull(spanContext.baggage?.sampleRate) + assertNull(spanContext.baggage?.transaction) + assertNull(spanContext.baggage?.publicKey) +// assertFalse(spanContext.baggage!!.isMutable) + } + + assertNotNull(sentrySpan.startDate) +// assertFalse(sentrySpan.isBindToScope) + } else { + assertNull(sentrySpan.description) + assertEquals("testspan", sentrySpan.operation) + assertEquals(otelSpan.spanContext.spanId, spanContext.spanId.toString()) + assertEquals(otelSpan.spanContext.traceId, spanContext.traceId.toString()) + assertNull(spanContext.parentSpanId) + assertNull(spanContext.baggage) + assertNotNull(sentrySpan.startDate) + } + } + + private fun thenTraceIdIsUsed(otelSpan: Span) { + assertEquals(SENTRY_TRACE_ID, otelSpan.spanContext.traceId) + } + + private fun thenTraceIdIsNotUsed(otelSpan: Span) { + assertNotEquals(SENTRY_TRACE_ID, otelSpan.spanContext.traceId) + } + + private fun thenNoSpanIsCreated(otelSpan: Span) { + val sentrySpan = SentryWeakSpanStorage.getInstance().getSentrySpan(otelSpan.spanContext) + assertNull(sentrySpan) + } + + private fun thenChildSpanIsCreated(otelParentSpan: Span, otelChildSpan: Span) { + val sentryParentSpan = SentryWeakSpanStorage.getInstance().getSentrySpan(otelParentSpan.spanContext) + val sentryChildSpan = SentryWeakSpanStorage.getInstance().getSentrySpan(otelChildSpan.spanContext) + assertNotNull(sentryParentSpan) + assertNotNull(sentryChildSpan) + assertEquals(sentryParentSpan.spanContext.spanId, sentryChildSpan.spanContext.parentSpanId) + assertEquals(sentryChildSpan.operation, "childspan") + } + + private fun thenSpanIsFinished(otelSpan: Span, status: SpanStatus = SpanStatus.OK) { + val sentrySpan = SentryWeakSpanStorage.getInstance().getSentrySpan(otelSpan.spanContext) + assertNotNull(sentrySpan) + assertTrue(sentrySpan.isFinished) + assertEquals(status, sentrySpan.status) + } +} + +class OtelHeaderGetter : TextMapGetter { + override fun keys(headers: HttpHeaders): MutableIterable { + return headers.map().map { it.key }.toMutableList() + } + + override fun get(headers: HttpHeaders?, key: String): String? { + return headers?.firstValue(key)?.orElse(null) + } +} + +class OtelTestSetter : TextMapSetter> { + override fun set(values: MutableMap?, key: String, value: String) { + values?.put(key, value) + } +} diff --git a/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanContext.java b/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanContext.java index 626b837c97..8081b55580 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanContext.java +++ b/sentry-opentelemetry/sentry-opentelemetry-extra/src/main/java/io/sentry/opentelemetry/OtelSpanContext.java @@ -113,4 +113,17 @@ public void setOperation(@NotNull String operation) { return StatusCode.ERROR; } } + + @Override + public @Nullable Boolean getSampled() { + Boolean superSampled = super.getSampled(); + if (superSampled != null) { + return superSampled; + } + final @Nullable ReadWriteSpan otelSpan = span.get(); + if (otelSpan != null) { + return otelSpan.getSpanContext().isSampled(); + } + return null; + } } From f564f342cecbd098ae606c093ca6288572fbbbcb Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Mon, 14 Oct 2024 14:20:48 +0200 Subject: [PATCH 2/5] format, api dump --- .../SpanDescriptionExtractor.java | 15 +++---- .../kotlin/OtelSentrySpanProcessorTest.kt | 41 ++++++++++++++----- .../api/sentry-opentelemetry-extra.api | 1 + 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java index 462cbdab8e..52e5799c32 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SpanDescriptionExtractor.java @@ -17,12 +17,13 @@ public final class SpanDescriptionExtractor { @SuppressWarnings("deprecation") public @NotNull OtelSpanInfo extractSpanInfo( - final @NotNull SpanData otelSpan, final @Nullable OtelSpanWrapper sentrySpan) { + final @NotNull SpanData otelSpan, final @Nullable OtelSpanWrapper sentrySpan) { final @NotNull Attributes attributes = otelSpan.getAttributes(); - final @Nullable String httpMethod = attributes.get(HttpAttributes.HTTP_REQUEST_METHOD) != null - ? attributes.get(HttpAttributes.HTTP_REQUEST_METHOD) - : attributes.get(io.opentelemetry.semconv.SemanticAttributes.HTTP_METHOD); + final @Nullable String httpMethod = + attributes.get(HttpAttributes.HTTP_REQUEST_METHOD) != null + ? attributes.get(HttpAttributes.HTTP_REQUEST_METHOD) + : attributes.get(io.opentelemetry.semconv.SemanticAttributes.HTTP_METHOD); if (httpMethod != null) { return descriptionForHttpMethod(otelSpan, httpMethod); } @@ -34,14 +35,14 @@ public final class SpanDescriptionExtractor { final @NotNull String name = otelSpan.getName(); final @Nullable String maybeDescription = - sentrySpan != null ? sentrySpan.getDescription() : name; + sentrySpan != null ? sentrySpan.getDescription() : name; final @NotNull String description = maybeDescription != null ? maybeDescription : name; return new OtelSpanInfo(name, description, TransactionNameSource.CUSTOM); } @SuppressWarnings("deprecation") private OtelSpanInfo descriptionForHttpMethod( - final @NotNull SpanData otelSpan, final @NotNull String httpMethod) { + final @NotNull SpanData otelSpan, final @NotNull String httpMethod) { final @NotNull String name = otelSpan.getName(); final @NotNull SpanKind kind = otelSpan.getKind(); final @NotNull StringBuilder opBuilder = new StringBuilder("http"); @@ -72,7 +73,7 @@ private OtelSpanInfo descriptionForHttpMethod( final @NotNull String description = httpMethod + " " + httpPath; final @NotNull TransactionNameSource transactionNameSource = - httpRoute != null ? TransactionNameSource.ROUTE : TransactionNameSource.URL; + httpRoute != null ? TransactionNameSource.ROUTE : TransactionNameSource.URL; return new OtelSpanInfo(op, description, transactionNameSource); } diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt index ae04ed9ca2..82668578e7 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt @@ -1,10 +1,14 @@ package io.sentry.opentelemetry import io.opentelemetry.api.OpenTelemetry -import io.opentelemetry.api.trace.* import io.opentelemetry.api.trace.Span +import io.opentelemetry.api.trace.SpanBuilder import io.opentelemetry.api.trace.SpanContext import io.opentelemetry.api.trace.SpanId +import io.opentelemetry.api.trace.SpanKind +import io.opentelemetry.api.trace.StatusCode +import io.opentelemetry.api.trace.TraceId +import io.opentelemetry.api.trace.Tracer import io.opentelemetry.context.Context import io.opentelemetry.context.propagation.ContextPropagators import io.opentelemetry.context.propagation.TextMapGetter @@ -15,17 +19,32 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider import io.opentelemetry.sdk.trace.export.BatchSpanProcessor import io.opentelemetry.semconv.HttpAttributes import io.opentelemetry.semconv.UrlAttributes -import io.sentry.* +import io.sentry.Hint +import io.sentry.IScopes +import io.sentry.SentryEvent +import io.sentry.SentryOptions +import io.sentry.SpanStatus import io.sentry.util.SpanUtils -import org.mockito.kotlin.* +import org.junit.Assert.assertTrue +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyNoInteractions +import org.mockito.kotlin.verifyNoMoreInteractions +import org.mockito.kotlin.whenever import java.net.http.HttpHeaders -import kotlin.test.* +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull class OtelSentrySpanProcessorTest { companion object { val SENTRY_TRACE_ID = "2722d9f6ec019ade60c776169d9a8904" - val SENTRY_TRACE_HEADER_STRING = "${SENTRY_TRACE_ID}-cedf5b7571cb4972-1" + val SENTRY_TRACE_HEADER_STRING = "$SENTRY_TRACE_ID-cedf5b7571cb4972-1" val BAGGAGE_HEADER_STRING = "sentry-public_key=502f25099c204a2fbf4cb16edc5975d1,sentry-sample_rate=1,sentry-trace_id=2722d9f6ec019ade60c776169d9a8904,sentry-transaction=HTTP%20GET" } @@ -35,8 +54,8 @@ class OtelSentrySpanProcessorTest { it.dsn = "https://key@sentry.io/proj" it.ignoredSpanOrigins = SpanUtils.ignoredSpanOriginsForOpenTelemetry() it.spanFactory = OtelSpanFactory() - it.enableTracing = true it.sampleRate = 1.0 + it.tracesSampleRate = 1.0 } val scopes = mock() lateinit var openTelemetry: OpenTelemetry @@ -57,7 +76,6 @@ class OtelSentrySpanProcessorTest { .setPropagators(ContextPropagators.create(OtelSentryPropagator())) .build() - tracer = openTelemetry.getTracer("sentry-test") } } @@ -176,7 +194,7 @@ class OtelSentrySpanProcessorTest { assertTrue(map.isNotEmpty()) - assertEquals("${SENTRY_TRACE_ID}-${otelSpan.spanContext.spanId}-1", map["sentry-trace"]) + assertEquals("$SENTRY_TRACE_ID-${otelSpan.spanContext.spanId}-1", map["sentry-trace"]) assertEquals(BAGGAGE_HEADER_STRING, map["baggage"]) endSpanWithStatus(otelChildSpan) @@ -274,9 +292,9 @@ class OtelSentrySpanProcessorTest { thenSpanIsFinished(otelSpan) } + // TODO [POTEL] @Test @Ignore - //TODO [POTEL] fun `links error to OTEL transaction`() { fixture.setup() val extractedContext = whenExtractingHeaders() @@ -286,7 +304,10 @@ class OtelSentrySpanProcessorTest { thenSentrySpanIsCreated(otelSpan, isContinued = true) otelSpan.makeCurrent().use { _ -> - val processedEvent = OpenTelemetryLinkErrorEventProcessor(fixture.scopes).process(SentryEvent(), Hint()) + val processedEvent = OpenTelemetryLinkErrorEventProcessor(fixture.scopes).process( + SentryEvent(), + Hint() + ) val traceContext = processedEvent!!.contexts.trace!! assertEquals(SENTRY_TRACE_ID, traceContext.traceId.toString()) diff --git a/sentry-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api b/sentry-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api index bb749a7df1..25eaaea03f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api +++ b/sentry-opentelemetry/sentry-opentelemetry-extra/api/sentry-opentelemetry-extra.api @@ -13,6 +13,7 @@ public final class io/sentry/opentelemetry/OtelContextScopesStorage : io/sentry/ public final class io/sentry/opentelemetry/OtelSpanContext : io/sentry/SpanContext { public fun (Lio/opentelemetry/sdk/trace/ReadWriteSpan;Lio/sentry/TracesSamplingDecision;Lio/sentry/opentelemetry/OtelSpanWrapper;Lio/sentry/SpanId;Lio/sentry/Baggage;)V public fun getOperation ()Ljava/lang/String; + public fun getSampled ()Ljava/lang/Boolean; public fun getStatus ()Lio/sentry/SpanStatus; public fun setOperation (Ljava/lang/String;)V public fun setStatus (Lio/sentry/SpanStatus;)V From 719dfa228d91ffdeb8b876c0d9cb23a7052686ed Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Tue, 12 Nov 2024 10:34:37 +0100 Subject: [PATCH 3/5] fix OtelSentrySpanProcessorTest, rename superSampled to isSuperSampled --- .../sentry/opentelemetry/OtelSpanContext.java | 6 ++-- .../kotlin/OtelSentrySpanProcessorTest.kt | 31 +------------------ 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanContext.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanContext.java index 3229910481..fb9659098f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanContext.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanContext.java @@ -116,9 +116,9 @@ public void setOperation(@NotNull String operation) { @Override public @Nullable Boolean getSampled() { - Boolean superSampled = super.getSampled(); - if (superSampled != null) { - return superSampled; + Boolean isSuperSampled = super.getSampled(); + if (isSuperSampled != null) { + return isSuperSampled; } final @Nullable ReadWriteSpan otelSpan = span.get(); if (otelSpan != null) { diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt index 82668578e7..df1dae7bb3 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt @@ -52,7 +52,7 @@ class OtelSentrySpanProcessorTest { val options = SentryOptions().also { it.dsn = "https://key@sentry.io/proj" - it.ignoredSpanOrigins = SpanUtils.ignoredSpanOriginsForOpenTelemetry() + it.ignoredSpanOrigins = SpanUtils.ignoredSpanOriginsForOpenTelemetry(false) it.spanFactory = OtelSpanFactory() it.sampleRate = 1.0 it.tracesSampleRate = 1.0 @@ -292,35 +292,6 @@ class OtelSentrySpanProcessorTest { thenSpanIsFinished(otelSpan) } - // TODO [POTEL] - @Test - @Ignore - fun `links error to OTEL transaction`() { - fixture.setup() - val extractedContext = whenExtractingHeaders() - - extractedContext.makeCurrent().use { _ -> - val otelSpan = givenSpanBuilder().startSpan() - thenSentrySpanIsCreated(otelSpan, isContinued = true) - - otelSpan.makeCurrent().use { _ -> - val processedEvent = OpenTelemetryLinkErrorEventProcessor(fixture.scopes).process( - SentryEvent(), - Hint() - ) - val traceContext = processedEvent!!.contexts.trace!! - - assertEquals(SENTRY_TRACE_ID, traceContext.traceId.toString()) - assertEquals(otelSpan.spanContext.spanId, traceContext.spanId.toString()) - assertEquals("cedf5b7571cb4972", traceContext.parentSpanId.toString()) - assertEquals("spanContextOp", traceContext.operation) - } - - otelSpan.end() -// thenTransactionIsFinished() - } - } - private fun givenSpanBuilder(spanKind: SpanKind = SpanKind.SERVER, parentSpan: Span? = null): SpanBuilder { val spanName = if (parentSpan == null) "testspan" else "childspan" val spanBuilder = fixture.tracer From 677aea34988592f4d5bb07fcf0ce1832bbd37df4 Mon Sep 17 00:00:00 2001 From: Sentry Github Bot Date: Tue, 12 Nov 2024 09:45:40 +0000 Subject: [PATCH 4/5] Format code --- .../src/test/kotlin/OtelSentrySpanProcessorTest.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt index df1dae7bb3..c3c0f6f7cf 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt @@ -19,9 +19,7 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider import io.opentelemetry.sdk.trace.export.BatchSpanProcessor import io.opentelemetry.semconv.HttpAttributes import io.opentelemetry.semconv.UrlAttributes -import io.sentry.Hint import io.sentry.IScopes -import io.sentry.SentryEvent import io.sentry.SentryOptions import io.sentry.SpanStatus import io.sentry.util.SpanUtils @@ -32,7 +30,6 @@ import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.verifyNoMoreInteractions import org.mockito.kotlin.whenever import java.net.http.HttpHeaders -import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse From 2389deda4043eb1de85a3555490ce98b559c6fdb Mon Sep 17 00:00:00 2001 From: Lukas Bloder Date: Mon, 7 Apr 2025 14:42:43 +0200 Subject: [PATCH 5/5] fix test --- .../api/sentry-opentelemetry-core.api | 1 + .../src/test/kotlin/OtelSentrySpanProcessorTest.kt | 8 +------- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api b/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api index f20ea0ab86..158b19d3e8 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api +++ b/sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api @@ -38,6 +38,7 @@ public final class io/sentry/opentelemetry/OtelSentrySpanProcessor : io/opentele public final class io/sentry/opentelemetry/OtelSpanContext : io/sentry/SpanContext { public fun (Lio/opentelemetry/sdk/trace/ReadWriteSpan;Lio/sentry/TracesSamplingDecision;Lio/sentry/opentelemetry/IOtelSpanWrapper;Lio/sentry/SpanId;Lio/sentry/Baggage;)V public fun getOperation ()Ljava/lang/String; + public fun getSampled ()Ljava/lang/Boolean; public fun getStatus ()Lio/sentry/SpanStatus; public fun setOperation (Ljava/lang/String;)V public fun setStatus (Lio/sentry/SpanStatus;)V diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt index c3c0f6f7cf..0a34171ba7 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/test/kotlin/OtelSentrySpanProcessorTest.kt @@ -22,7 +22,6 @@ import io.opentelemetry.semconv.UrlAttributes import io.sentry.IScopes import io.sentry.SentryOptions import io.sentry.SpanStatus -import io.sentry.util.SpanUtils import org.junit.Assert.assertTrue import org.mockito.kotlin.mock import org.mockito.kotlin.verify @@ -49,7 +48,6 @@ class OtelSentrySpanProcessorTest { val options = SentryOptions().also { it.dsn = "https://key@sentry.io/proj" - it.ignoredSpanOrigins = SpanUtils.ignoredSpanOriginsForOpenTelemetry(false) it.spanFactory = OtelSpanFactory() it.sampleRate = 1.0 it.tracesSampleRate = 1.0 @@ -326,7 +324,6 @@ class OtelSentrySpanProcessorTest { if (isContinued) { assertNull(spanContext.description) -// assertEquals(TransactionNameSource.CUSTOM, spanContext.transactionNameSource) assertEquals("testspan", spanContext.operation) assertEquals(otelSpan.spanContext.spanId, spanContext.spanId.toString()) assertEquals(SENTRY_TRACE_ID, spanContext.traceId.toString()) @@ -337,7 +334,7 @@ class OtelSentrySpanProcessorTest { val baggage = spanContext.baggage assertNotNull(baggage) assertEquals(SENTRY_TRACE_ID, baggage.traceId) - assertEquals("1", baggage.sampleRate) + assertEquals(1.0, baggage.sampleRate) assertEquals("HTTP GET", baggage.transaction) assertEquals("502f25099c204a2fbf4cb16edc5975d1", baggage.publicKey) assertFalse(baggage.isMutable) @@ -347,18 +344,15 @@ class OtelSentrySpanProcessorTest { assertNull(spanContext.baggage?.sampleRate) assertNull(spanContext.baggage?.transaction) assertNull(spanContext.baggage?.publicKey) -// assertFalse(spanContext.baggage!!.isMutable) } assertNotNull(sentrySpan.startDate) -// assertFalse(sentrySpan.isBindToScope) } else { assertNull(sentrySpan.description) assertEquals("testspan", sentrySpan.operation) assertEquals(otelSpan.spanContext.spanId, spanContext.spanId.toString()) assertEquals(otelSpan.spanContext.traceId, spanContext.traceId.toString()) assertNull(spanContext.parentSpanId) - assertNull(spanContext.baggage) assertNotNull(sentrySpan.startDate) } }