Skip to content

E2E tests for OpenTelemetry based console sample #4563

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

Merged
merged 23 commits into from
Jul 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1ce9608
e2e tests for console app
adinauer Jul 10, 2025
a863ac5
fix test failures by waiting for 10s after first try to find envelopes
adinauer Jul 11, 2025
811c9da
add system-test-runner.py script to replace bash scripts for running …
adinauer Jul 11, 2025
16dcaee
use py script for ci, cleanup, makefile
adinauer Jul 11, 2025
5d9f010
Format code
getsentry-bot Jul 11, 2025
bee800a
Merge branch 'main' into feat/e2e-console-app-samples
adinauer Jul 11, 2025
e531d9d
remove bash scripts
adinauer Jul 11, 2025
9d01916
install requests module
adinauer Jul 11, 2025
105e0d0
api
adinauer Jul 11, 2025
195937c
fix gh script
adinauer Jul 11, 2025
d5fd3b0
Implement E2E tests for OTel based console sample
adinauer Jul 21, 2025
521850d
Merge branch 'main' into feat/e2e-tests-console-otel
adinauer Jul 21, 2025
0ac58fd
fixes after merge
adinauer Jul 21, 2025
b5f2392
Format code
getsentry-bot Jul 21, 2025
50b18c1
e2e tests for console app
adinauer Jul 10, 2025
8462370
Implement E2E tests for OTel based console sample
adinauer Jul 21, 2025
1dc620e
fixes after merge
adinauer Jul 21, 2025
44adaed
Merge branch 'feat/e2e-tests-console-otel' of github.com:getsentry/se…
adinauer Jul 29, 2025
eac7861
Format code
getsentry-bot Jul 29, 2025
bbc43cb
Merge branch 'main' into feat/e2e-tests-console-otel
adinauer Jul 29, 2025
ab5be77
api
adinauer Jul 29, 2025
a889829
Reduce scope forking when using OpenTelemetry (#4565)
adinauer Jul 29, 2025
2e6b3a3
SDKs send queue is no longer shutdown immediately on re-init (#4564)
adinauer Jul 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
- This was causing Sentry SDK to log warnings: "Sentry Log is disabled and this 'logger' call is a no-op."
- Do not use Sentry logging API in Log4j2 if logs are disabled ([#4573](https://github.com/getsentry/sentry-java/pull/4573))
- This was causing Sentry SDK to log warnings: "Sentry Log is disabled and this 'logger' call is a no-op."
- SDKs send queue is no longer shutdown immediately on re-init ([#4564](https://github.com/getsentry/sentry-java/pull/4564))
- This means we're no longer losing events that have been enqueued right before SDK re-init.
- Reduce scope forking when using OpenTelemetry ([#4565](https://github.com/getsentry/sentry-java/pull/4565))
- `Sentry.withScope` now has the correct current scope passed to the callback. Previously our OpenTelemetry integration forked scopes an additional.
- Overall the SDK is now forking scopes a bit less often.

## 8.17.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ public final class io/sentry/opentelemetry/SentryContextStorage : io/opentelemet
public fun <init> (Lio/opentelemetry/context/ContextStorage;)V
public fun attach (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Scope;
public fun current ()Lio/opentelemetry/context/Context;
public fun root ()Lio/opentelemetry/context/Context;
}

public final class io/sentry/opentelemetry/SentryContextStorageProvider : io/opentelemetry/context/ContextStorageProvider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ public Scope attach(Context toAttach) {
public Context current() {
return contextStorage.current();
}

@Override
public Context root() {
return SentryContextWrapper.wrap(ContextStorage.super.root());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public <V> Context with(final @NotNull ContextKey<V> contextKey, V v) {
if (isOpentelemetrySpan(contextKey)) {
return forkCurrentScope(modifiedContext);
} else {
return modifiedContext;
return new SentryContextWrapper(modifiedContext);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,83 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
java
application
kotlin("jvm")
alias(libs.plugins.gradle.versions)
id("com.github.johnrengelman.shadow") version "8.1.1"
}

application { mainClass.set("io.sentry.samples.console.Main") }

java.sourceCompatibility = JavaVersion.VERSION_17

java.targetCompatibility = JavaVersion.VERSION_17

repositories { mavenCentral() }

configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
}

tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = JavaVersion.VERSION_17.toString()
}
}

dependencies {
implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentless)

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)
tasks.shadowJar {
manifest { attributes["Main-Class"] = "io.sentry.samples.console.Main" }
archiveClassifier.set("") // Remove the classifier so it replaces the regular JAR
mergeServiceFiles()
}

dependencies { implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentless) }
// Make the regular jar task depend on shadowJar
tasks.jar {
enabled = false
dependsOn(tasks.shadowJar)
}

// Fix the startScripts task dependency
tasks.startScripts { dependsOn(tasks.shadowJar) }

configure<SourceSetContainer> { test { java.srcDir("src/test/java") } }

tasks.register<Test>("systemTest").configure {
group = "verification"
description = "Runs the System tests"

outputs.upToDateWhen { false }

maxParallelForks = 1

// Cap JVM args per test
minHeapSize = "128m"
maxHeapSize = "1g"

filter { includeTestsMatching("io.sentry.systemtest*") }
}

tasks.named("test").configure {
require(this is Test)

filter { excludeTestsMatching("io.sentry.systemtest.*") }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.StatusCode;
import io.opentelemetry.context.Scope;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.sentry.Breadcrumb;
import io.sentry.EventProcessor;
import io.sentry.Hint;
Expand All @@ -17,90 +18,23 @@
import io.sentry.protocol.Message;
import io.sentry.protocol.User;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class Main {

public static void main(String[] args) throws InterruptedException {
Sentry.init(
options -> {
// NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in
// your Sentry project/dashboard
options.setDsn(
"https://[email protected]/5428563");

// All events get assigned to the release. See more at
// https://docs.sentry.io/workflow/releases/
options.setRelease("[email protected]+1");

// Modifications to event before it goes out. Could replace the event altogether
options.setBeforeSend(
(event, hint) -> {
// Drop an event altogether:
if (event.getTag("SomeTag") != null) {
return null;
}
return event;
});

options.setBeforeSendTransaction(
(transaction, hint) -> {
// Drop a transaction:
if (transaction.getTag("SomeTransactionTag") != null) {
return null;
}

return transaction;
});

// Allows inspecting and modifying, returning a new or simply rejecting (returning null)
options.setBeforeBreadcrumb(
(breadcrumb, hint) -> {
// Don't add breadcrumbs with message containing:
if (breadcrumb.getMessage() != null
&& breadcrumb.getMessage().contains("bad breadcrumb")) {
return null;
}
return breadcrumb;
});

// Configure the background worker which sends events to sentry:
// Wait up to 5 seconds before shutdown while there are events to send.
options.setShutdownTimeoutMillis(5000);

// Enable SDK logging with Debug level
options.setDebug(true);
// To change the verbosity, use:
// By default it's DEBUG.
// options.setDiagnosticLevel(
// SentryLevel
// .ERROR); // A good option to have SDK debug log in prod is to use
// only level
// ERROR here.
options.setEnablePrettySerializationOutput(false);

// Exclude frames from some packages from being "inApp" so are hidden by default in Sentry
// UI:
options.addInAppExclude("org.jboss");

// Include frames from our package
options.addInAppInclude("io.sentry.samples");

// Performance configuration options
// Set what percentage of traces should be collected
options.setTracesSampleRate(1.0); // set 0.5 to send 50% of traces

// Determine traces sample rate based on the sampling context
// options.setTracesSampler(
// context -> {
// // only 10% of transactions with "/product" prefix will be collected
// if (!context.getTransactionContext().getName().startsWith("/products"))
// {
// return 0.1;
// } else {
// return 0.5;
// }
// });
});
AutoConfiguredOpenTelemetrySdk.builder()
.setResultAsGlobal()
.addPropertiesSupplier(
() -> {
final Map<String, String> properties = new HashMap<>();
properties.put("otel.logs.exporter", "none");
properties.put("otel.metrics.exporter", "none");
properties.put("otel.traces.exporter", "none");
return properties;
})
.build();

Sentry.addBreadcrumb(
"A 'bad breadcrumb' that will be rejected because of 'BeforeBreadcrumb callback above.'");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
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-opentelemetry-noagent")
val process =
testHelper.launch(
jarFile,
mapOf(
"SENTRY_DSN" to testHelper.dsn,
// "SENTRY_AUTO_INIT" to "false",
"SENTRY_TRACES_SAMPLE_RATE" to "1.0",
"SENTRY_ENABLE_PRETTY_SERIALIZATION_OUTPUT" to "false",
"SENTRY_DEBUG" to "true",
"OTEL_METRICS_EXPORTER" to "none",
"OTEL_LOGS_EXPORTER" to "none",
"OTEL_TRACES_EXPORTER" to "none",
),
)

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
}

// 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 transaction events
testHelper.ensureTransactionReceived { transaction, _ ->
transaction.transaction == "transaction name" &&
transaction.spans?.any { span -> span.op == "child" } == true
}

// 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 have breadcrumbs
testHelper.ensureErrorReceived { event ->
event.breadcrumbs?.any { breadcrumb ->
breadcrumb.message?.contains("Processed by") == true
} == true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public static void main(String[] args) throws InterruptedException {
// Enable SDK logging with Debug level
options.setDebug(true);
// To change the verbosity, use:
// options.setDiagnosticLevel(SentryLevel.ERROR);
// By default it's DEBUG.
// options.setDiagnosticLevel(SentryLevel.ERROR);
// A good option to have SDK debug log in prod is to use only level ERROR here.

// Exclude frames from some packages from being "inApp" so are hidden by default in Sentry
Expand All @@ -82,15 +82,16 @@ public static void main(String[] args) throws InterruptedException {
options.setTracesSampleRate(1.0); // set 0.5 to send 50% of traces

// Determine traces sample rate based on the sampling context
// options.setTracesSampler(
// context -> {
// // only 10% of transactions with "/product" prefix will be collected
// if (!context.getTransactionContext().getName().startsWith("/products")) {
// return 0.1;
// } else {
// return 0.5;
// }
// });
// options.setTracesSampler(
// context -> {
// // only 10% of transactions with "/product" prefix will be collected
// if (!context.getTransactionContext().getName().startsWith("/products"))
// {
// return 0.1;
// } else {
// return 0.5;
// }
// });
});

Sentry.addBreadcrumb(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,8 @@ public final class io/sentry/systemtest/util/TestHelper {
public final fun getJsonSerializer ()Lio/sentry/JsonSerializer;
public final fun getRestClient ()Lio/sentry/systemtest/util/RestTestClient;
public final fun getSentryClient ()Lio/sentry/systemtest/util/SentryMockServerClient;
public final fun launch (Ljava/io/File;Ljava/util/Map;)Ljava/lang/Process;
public final fun launch (Ljava/io/File;Ljava/util/Map;Z)Ljava/lang/Process;
public static synthetic fun launch$default (Lio/sentry/systemtest/util/TestHelper;Ljava/io/File;Ljava/util/Map;ZILjava/lang/Object;)Ljava/lang/Process;
public final fun logObject (Ljava/lang/Object;)V
public final fun reset ()V
public final fun setEnvelopeCounts (Lio/sentry/systemtest/util/EnvelopeCounts;)V
Expand Down
Loading
Loading