diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml index 19c480ab0a5..0212b0f58da 100644 --- a/.github/workflows/system-tests-backend.yml +++ b/.github/workflows/system-tests-backend.yml @@ -24,13 +24,9 @@ jobs: strategy: fail-fast: false matrix: - sample: [ "sentry-samples-console-otlp" ] + sample: [ "sentry-samples-spring-boot-4-otlp" ] agent: [ "false" ] agent-auto-init: [ "true" ] - include: - - sample: "sentry-samples-spring-boot-4-otlp" - agent: "false" - agent-auto-init: "true" steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/sentry-opentelemetry/sentry-opentelemetry-otlp/build.gradle.kts b/sentry-opentelemetry/sentry-opentelemetry-otlp/build.gradle.kts index afab9ef180f..fb95d722d6f 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-otlp/build.gradle.kts +++ b/sentry-opentelemetry/sentry-opentelemetry-otlp/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { testImplementation(projects.sentryTestSupport) testImplementation(kotlin(Config.kotlinStdLib)) testImplementation(libs.awaitility.kotlin) + testImplementation(libs.google.truth) testImplementation(libs.kotlin.test.junit) testImplementation(libs.mockito.kotlin) diff --git a/sentry-opentelemetry/sentry-opentelemetry-otlp/src/test/kotlin/OtlpEndToEndTest.kt b/sentry-opentelemetry/sentry-opentelemetry-otlp/src/test/kotlin/OtlpEndToEndTest.kt new file mode 100644 index 00000000000..fb3db9731e8 --- /dev/null +++ b/sentry-opentelemetry/sentry-opentelemetry-otlp/src/test/kotlin/OtlpEndToEndTest.kt @@ -0,0 +1,66 @@ +package io.sentry.opentelemetry.otlp + +import com.google.common.truth.Truth.assertThat +import io.opentelemetry.sdk.OpenTelemetrySdk +import io.opentelemetry.sdk.trace.SdkTracerProvider +import io.sentry.Sentry +import io.sentry.test.MockSentryServer +import io.sentry.test.event +import io.sentry.test.hasMetric +import io.sentry.test.initForTest +import io.sentry.test.logs +import io.sentry.test.metrics +import kotlin.test.AfterTest +import kotlin.test.Test + +/** + * An app that exports its OpenTelemetry spans over OTLP and reports everything else to Sentry, as + * the console OTLP sample does. + */ +class OtlpEndToEndTest { + private val server = MockSentryServer() + private val openTelemetry = + OpenTelemetrySdk.builder().setTracerProvider(SdkTracerProvider.builder().build()).build() + + @AfterTest + fun teardown() { + Sentry.close() + openTelemetry.close() + server.close() + } + + @Test + fun `an event inside an OpenTelemetry span is linked to it`() { + initForTest { + it.dsn = server.dsn + it.logs.isEnabled = true + it.addEventProcessor(OpenTelemetryOtlpEventProcessor()) + } + + val span = openTelemetry.getTracer("otlp-end-to-end-test").spanBuilder("otelSpan").startSpan() + span.makeCurrent().use { + Sentry.captureMessage("this message is connected to the outerSpan") + Sentry.logger().error("Some error log message") + Sentry.metrics().count("invocations") + } + span.end() + Sentry.flush(5000) + + val event = + server + .awaitEnvelope { + it.event()?.message?.formatted == "this message is connected to the outerSpan" + } + .event()!! + // The span's trace goes over OTLP, so the event points to it rather than to a Sentry span. + val trace = event.contexts.trace!! + assertThat(trace.traceId.toString()).isEqualTo(span.spanContext.traceId) + assertThat(trace.spanId.toString()).isEqualTo(span.spanContext.spanId) + + val logs = server.awaitEnvelope { it.logs() != null }.logs()!! + assertThat(logs.items.map { it.body }).contains("Some error log message") + + val metrics = server.awaitEnvelope { it.metrics() != null }.metrics()!! + assertThat(metrics.hasMetric("invocations", "counter", 1.0)).isTrue() + } +} diff --git a/sentry-samples/sentry-samples-console-otlp/build.gradle.kts b/sentry-samples/sentry-samples-console-otlp/build.gradle.kts index 58a8578c7d1..134697014b7 100644 --- a/sentry-samples/sentry-samples-console-otlp/build.gradle.kts +++ b/sentry-samples/sentry-samples-console-otlp/build.gradle.kts @@ -1,35 +1,18 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - plugins { java application - alias(libs.plugins.kotlin.jvm) alias(libs.plugins.gradle.versions) alias(libs.plugins.shadow) - id("io.sentry.systemtest") } application { mainClass.set("io.sentry.samples.console.Main") } -tasks.withType().configureEach { - kotlin { - compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - } -} - dependencies { implementation(projects.sentryOpentelemetry.sentryOpentelemetryOtlp) implementation(projects.sentryAsyncProfiler) implementation(libs.otel.semconv) implementation(libs.otel.semconv.incubating) implementation(libs.otel.exporter.logging) - - testImplementation(kotlin(Config.kotlinStdLib)) - testImplementation(projects.sentry) - testImplementation(projects.sentrySystemTestSupport) - testImplementation(libs.kotlin.test.junit) - testImplementation(libs.slf4j.api) - testImplementation(libs.slf4j.jdk14) } // Configure the Shadow JAR (executable JAR with all dependencies) @@ -48,22 +31,3 @@ tasks.jar { // Fix the startScripts task dependency tasks.startScripts { dependsOn(tasks.shadowJar) } - -tasks.register("systemTest").configure { - group = "verification" - description = "Runs the System tests" - - val test = project.extensions.getByType()["test"] - testClassesDirs = test.output.classesDirs - classpath = test.runtimeClasspath - - maxParallelForks = 1 - - filter { includeTestsMatching("io.sentry.systemtest*") } -} - -tasks.named("test").configure { - require(this is Test) - - filter { excludeTestsMatching("io.sentry.systemtest.*") } -} diff --git a/sentry-samples/sentry-samples-console-otlp/src/test/kotlin/io/sentry/DummyTest.kt b/sentry-samples/sentry-samples-console-otlp/src/test/kotlin/io/sentry/DummyTest.kt deleted file mode 100644 index 6f762b7e453..00000000000 --- a/sentry-samples/sentry-samples-console-otlp/src/test/kotlin/io/sentry/DummyTest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package io.sentry - -import kotlin.test.Test -import kotlin.test.assertTrue - -class DummyTest { - @Test - fun `the only test`() { - // only needed to have more than 0 tests and not fail the build - assertTrue(true) - } -} diff --git a/sentry-samples/sentry-samples-console-otlp/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt b/sentry-samples/sentry-samples-console-otlp/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt deleted file mode 100644 index 1333fa50387..00000000000 --- a/sentry-samples/sentry-samples-console-otlp/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt +++ /dev/null @@ -1,107 +0,0 @@ -package io.sentry.systemtest - -import io.sentry.systemtest.util.TestHelper -import java.util.concurrent.TimeUnit -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test - -class ConsoleApplicationSystemTest { - lateinit var testHelper: TestHelper - - @Before - fun setup() { - testHelper = TestHelper("http://localhost:8000") - testHelper.reset() - } - - @Test - fun `console application sends expected events when run as JAR`() { - val jarFile = testHelper.findJar("sentry-samples-console-otlp") - val process = - testHelper.launch( - jarFile, - mapOf( - "SENTRY_DSN" to testHelper.dsn, - "SENTRY_ENABLE_PRETTY_SERIALIZATION_OUTPUT" to "false", - "SENTRY_DEBUG" to "true", - ), - ) - - process.waitFor(30, TimeUnit.SECONDS) - assertEquals(0, process.exitValue()) - - // Verify that we received the expected events - verifyExpectedEvents() - } - - private fun verifyExpectedEvents() { - // Verify we received a "Fatal message!" event - testHelper.ensureErrorReceived { event -> - event.message?.formatted == "Fatal message!" && event.level?.name == "FATAL" - } - - // Verify we received a "Some warning!" event - testHelper.ensureErrorReceived { event -> - event.message?.formatted == "Some warning!" && event.level?.name == "WARNING" - } - - // Verify we received the RuntimeException - testHelper.ensureErrorReceived { event -> - event.exceptions?.any { ex -> ex.type == "RuntimeException" && ex.value == "Some error!" } == - true && testHelper.doesEventHaveFlag(event, "my-feature-flag", true) - } - - // Verify we received the detailed event with fingerprint - testHelper.ensureErrorReceived { event -> - event.message?.message == "Detailed event" && - event.fingerprints?.contains("NewClientDebug") == true && - event.level?.name == "DEBUG" - } - - // Verify we received the loop messages (should be 10 of them) - var loopMessageCount = 0 - try { - for (i in 0..9) { - testHelper.ensureErrorReceived { event -> - val matches = - event.message?.message?.contains("items we'll wait to flush to Sentry!") == true - if (matches) loopMessageCount++ - matches - } - } - } catch (e: Exception) { - // Some loop messages might be missing, but we should have at least some - } - - assertTrue( - "Should receive at least 5 loop messages, got $loopMessageCount", - loopMessageCount >= 5, - ) - - // Verify we received the message captured within the OTel span - testHelper.ensureErrorReceived { event -> - event.message?.formatted == "this message is connected to the outerSpan" - } - - // Verify we have breadcrumbs - testHelper.ensureErrorReceived { event -> - event.breadcrumbs?.any { breadcrumb -> - breadcrumb.message?.contains("Processed by") == true - } == true - } - - testHelper.ensureMetricsReceived { metricsEvents, sentryEnvelopeHeader -> - testHelper.doesContainMetric(metricsEvents, "countMetric", "counter", 1.0) && - testHelper.doesContainMetric(metricsEvents, "gaugeMetric", "gauge", 5.0) && - testHelper.doesContainMetric(metricsEvents, "distributionMetric", "distribution", 7.0) && - testHelper.doesContainMetric(metricsEvents, "invocations", "counter", 1.0) - } - - // Verify we received the log message captured within the OTel span - testHelper.ensureLogsReceived { logs, _ -> - testHelper.doesContainLogWithBody(logs, "Some error log message") - } - } -} diff --git a/test/system-test-runner.py b/test/system-test-runner.py index 0a58f63ff1b..6115fa61680 100644 --- a/test/system-test-runner.py +++ b/test/system-test-runner.py @@ -826,7 +826,6 @@ def get_available_modules(self) -> List[ModuleConfig]: """Get list of all available test modules.""" return [ ModuleConfig("sentry-samples-spring-boot-4-otlp", "false", "true", "false"), - ModuleConfig("sentry-samples-console-otlp", "false", "true", "false"), ] def _find_module_number(self, module_name: str, agent: str, auto_init: str) -> int: