Skip to content

Commit d521a60

Browse files
E2E tests for OpenTelemetry based console sample (#4563)
* e2e tests for console app * fix test failures by waiting for 10s after first try to find envelopes * add system-test-runner.py script to replace bash scripts for running e2e / system tests * use py script for ci, cleanup, makefile * Format code * remove bash scripts * install requests module * api * fix gh script * Implement E2E tests for OTel based console sample * fixes after merge * Format code * e2e tests for console app * Implement E2E tests for OTel based console sample * fixes after merge * Format code * api * Reduce scope forking when using OpenTelemetry (#4565) * Reduce scope forking in OpenTelemetry * Format code * api * changelog --------- Co-authored-by: Sentry Github Bot <[email protected]> * SDKs send queue is no longer shutdown immediately on re-init (#4564) * Let queue drain on a restart * Format code * Format code * Update sentry-samples/sentry-samples-console-opentelemetry-noagent/src/test/kotlin/sentry/systemtest/ConsoleApplicationSystemTest.kt * Let queue drain on a restart * Format code * Format code * Update sentry-samples/sentry-samples-console-opentelemetry-noagent/src/test/kotlin/sentry/systemtest/ConsoleApplicationSystemTest.kt * adapt tests * changelog --------- Co-authored-by: Sentry Github Bot <[email protected]> --------- Co-authored-by: Sentry Github Bot <[email protected]>
1 parent 0d0c782 commit d521a60

File tree

15 files changed

+269
-119
lines changed

15 files changed

+269
-119
lines changed

CHANGELOG.md

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

3439
## 8.17.0
3540

sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ public final class io/sentry/opentelemetry/SentryContextStorage : io/opentelemet
151151
public fun <init> (Lio/opentelemetry/context/ContextStorage;)V
152152
public fun attach (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Scope;
153153
public fun current ()Lio/opentelemetry/context/Context;
154+
public fun root ()Lio/opentelemetry/context/Context;
154155
}
155156

156157
public final class io/sentry/opentelemetry/SentryContextStorageProvider : io/opentelemetry/context/ContextStorageProvider {

sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/SentryContextStorage.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,9 @@ public Scope attach(Context toAttach) {
3838
public Context current() {
3939
return contextStorage.current();
4040
}
41+
42+
@Override
43+
public Context root() {
44+
return SentryContextWrapper.wrap(ContextStorage.super.root());
45+
}
4146
}

sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/SentryContextWrapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public <V> Context with(final @NotNull ContextKey<V> contextKey, V v) {
3232
if (isOpentelemetrySpan(contextKey)) {
3333
return forkCurrentScope(modifiedContext);
3434
} else {
35-
return modifiedContext;
35+
return new SentryContextWrapper(modifiedContext);
3636
}
3737
}
3838

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,83 @@
1+
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2+
13
plugins {
24
java
35
application
6+
kotlin("jvm")
47
alias(libs.plugins.gradle.versions)
8+
id("com.github.johnrengelman.shadow") version "8.1.1"
59
}
610

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

13+
java.sourceCompatibility = JavaVersion.VERSION_17
14+
15+
java.targetCompatibility = JavaVersion.VERSION_17
16+
17+
repositories { mavenCentral() }
18+
919
configure<JavaPluginExtension> {
10-
sourceCompatibility = JavaVersion.VERSION_1_8
11-
targetCompatibility = JavaVersion.VERSION_1_8
20+
sourceCompatibility = JavaVersion.VERSION_17
21+
targetCompatibility = JavaVersion.VERSION_17
22+
}
23+
24+
tasks.withType<KotlinCompile>().configureEach {
25+
kotlinOptions.jvmTarget = JavaVersion.VERSION_17.toString()
26+
}
27+
28+
tasks.withType<KotlinCompile>().configureEach {
29+
kotlinOptions {
30+
freeCompilerArgs = listOf("-Xjsr305=strict")
31+
jvmTarget = JavaVersion.VERSION_17.toString()
32+
}
33+
}
34+
35+
dependencies {
36+
implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentless)
37+
38+
testImplementation(kotlin(Config.kotlinStdLib))
39+
testImplementation(projects.sentry)
40+
testImplementation(projects.sentrySystemTestSupport)
41+
testImplementation(libs.kotlin.test.junit)
42+
testImplementation(libs.slf4j.api)
43+
testImplementation(libs.slf4j.jdk14)
44+
}
45+
46+
// Configure the Shadow JAR (executable JAR with all dependencies)
47+
tasks.shadowJar {
48+
manifest { attributes["Main-Class"] = "io.sentry.samples.console.Main" }
49+
archiveClassifier.set("") // Remove the classifier so it replaces the regular JAR
50+
mergeServiceFiles()
1251
}
1352

14-
dependencies { implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentless) }
53+
// Make the regular jar task depend on shadowJar
54+
tasks.jar {
55+
enabled = false
56+
dependsOn(tasks.shadowJar)
57+
}
58+
59+
// Fix the startScripts task dependency
60+
tasks.startScripts { dependsOn(tasks.shadowJar) }
61+
62+
configure<SourceSetContainer> { test { java.srcDir("src/test/java") } }
63+
64+
tasks.register<Test>("systemTest").configure {
65+
group = "verification"
66+
description = "Runs the System tests"
67+
68+
outputs.upToDateWhen { false }
69+
70+
maxParallelForks = 1
71+
72+
// Cap JVM args per test
73+
minHeapSize = "128m"
74+
maxHeapSize = "1g"
75+
76+
filter { includeTestsMatching("io.sentry.systemtest*") }
77+
}
78+
79+
tasks.named("test").configure {
80+
require(this is Test)
81+
82+
filter { excludeTestsMatching("io.sentry.systemtest.*") }
83+
}

sentry-samples/sentry-samples-console-opentelemetry-noagent/src/main/java/io/sentry/samples/console/Main.java

Lines changed: 14 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.opentelemetry.api.trace.Span;
55
import io.opentelemetry.api.trace.StatusCode;
66
import io.opentelemetry.context.Scope;
7+
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
78
import io.sentry.Breadcrumb;
89
import io.sentry.EventProcessor;
910
import io.sentry.Hint;
@@ -17,90 +18,23 @@
1718
import io.sentry.protocol.Message;
1819
import io.sentry.protocol.User;
1920
import java.util.Collections;
21+
import java.util.HashMap;
22+
import java.util.Map;
2023

2124
public class Main {
2225

2326
public static void main(String[] args) throws InterruptedException {
24-
Sentry.init(
25-
options -> {
26-
// NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in
27-
// your Sentry project/dashboard
28-
options.setDsn(
29-
"https://[email protected]/5428563");
30-
31-
// All events get assigned to the release. See more at
32-
// https://docs.sentry.io/workflow/releases/
33-
options.setRelease("[email protected]+1");
34-
35-
// Modifications to event before it goes out. Could replace the event altogether
36-
options.setBeforeSend(
37-
(event, hint) -> {
38-
// Drop an event altogether:
39-
if (event.getTag("SomeTag") != null) {
40-
return null;
41-
}
42-
return event;
43-
});
44-
45-
options.setBeforeSendTransaction(
46-
(transaction, hint) -> {
47-
// Drop a transaction:
48-
if (transaction.getTag("SomeTransactionTag") != null) {
49-
return null;
50-
}
51-
52-
return transaction;
53-
});
54-
55-
// Allows inspecting and modifying, returning a new or simply rejecting (returning null)
56-
options.setBeforeBreadcrumb(
57-
(breadcrumb, hint) -> {
58-
// Don't add breadcrumbs with message containing:
59-
if (breadcrumb.getMessage() != null
60-
&& breadcrumb.getMessage().contains("bad breadcrumb")) {
61-
return null;
62-
}
63-
return breadcrumb;
64-
});
65-
66-
// Configure the background worker which sends events to sentry:
67-
// Wait up to 5 seconds before shutdown while there are events to send.
68-
options.setShutdownTimeoutMillis(5000);
69-
70-
// Enable SDK logging with Debug level
71-
options.setDebug(true);
72-
// To change the verbosity, use:
73-
// By default it's DEBUG.
74-
// options.setDiagnosticLevel(
75-
// SentryLevel
76-
// .ERROR); // A good option to have SDK debug log in prod is to use
77-
// only level
78-
// ERROR here.
79-
options.setEnablePrettySerializationOutput(false);
80-
81-
// Exclude frames from some packages from being "inApp" so are hidden by default in Sentry
82-
// UI:
83-
options.addInAppExclude("org.jboss");
84-
85-
// Include frames from our package
86-
options.addInAppInclude("io.sentry.samples");
87-
88-
// Performance configuration options
89-
// Set what percentage of traces should be collected
90-
options.setTracesSampleRate(1.0); // set 0.5 to send 50% of traces
91-
92-
// Determine traces sample rate based on the sampling context
93-
// options.setTracesSampler(
94-
// context -> {
95-
// // only 10% of transactions with "/product" prefix will be collected
96-
// if (!context.getTransactionContext().getName().startsWith("/products"))
97-
// {
98-
// return 0.1;
99-
// } else {
100-
// return 0.5;
101-
// }
102-
// });
103-
});
27+
AutoConfiguredOpenTelemetrySdk.builder()
28+
.setResultAsGlobal()
29+
.addPropertiesSupplier(
30+
() -> {
31+
final Map<String, String> properties = new HashMap<>();
32+
properties.put("otel.logs.exporter", "none");
33+
properties.put("otel.metrics.exporter", "none");
34+
properties.put("otel.traces.exporter", "none");
35+
return properties;
36+
})
37+
.build();
10438

10539
Sentry.addBreadcrumb(
10640
"A 'bad breadcrumb' that will be rejected because of 'BeforeBreadcrumb callback above.'");
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.sentry
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertTrue
5+
6+
class DummyTest {
7+
@Test
8+
fun `the only test`() {
9+
// only needed to have more than 0 tests and not fail the build
10+
assertTrue(true)
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package io.sentry.systemtest
2+
3+
import io.sentry.systemtest.util.TestHelper
4+
import java.util.concurrent.TimeUnit
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertTrue
7+
import org.junit.Before
8+
import org.junit.Test
9+
10+
class ConsoleApplicationSystemTest {
11+
lateinit var testHelper: TestHelper
12+
13+
@Before
14+
fun setup() {
15+
testHelper = TestHelper("http://localhost:8000")
16+
testHelper.reset()
17+
}
18+
19+
@Test
20+
fun `console application sends expected events when run as JAR`() {
21+
val jarFile = testHelper.findJar("sentry-samples-console-opentelemetry-noagent")
22+
val process =
23+
testHelper.launch(
24+
jarFile,
25+
mapOf(
26+
"SENTRY_DSN" to testHelper.dsn,
27+
// "SENTRY_AUTO_INIT" to "false",
28+
"SENTRY_TRACES_SAMPLE_RATE" to "1.0",
29+
"SENTRY_ENABLE_PRETTY_SERIALIZATION_OUTPUT" to "false",
30+
"SENTRY_DEBUG" to "true",
31+
"OTEL_METRICS_EXPORTER" to "none",
32+
"OTEL_LOGS_EXPORTER" to "none",
33+
"OTEL_TRACES_EXPORTER" to "none",
34+
),
35+
)
36+
37+
process.waitFor(30, TimeUnit.SECONDS)
38+
assertEquals(0, process.exitValue())
39+
40+
// Verify that we received the expected events
41+
verifyExpectedEvents()
42+
}
43+
44+
private fun verifyExpectedEvents() {
45+
// Verify we received a "Fatal message!" event
46+
testHelper.ensureErrorReceived { event ->
47+
event.message?.formatted == "Fatal message!" && event.level?.name == "FATAL"
48+
}
49+
50+
// Verify we received a "Some warning!" event
51+
testHelper.ensureErrorReceived { event ->
52+
event.message?.formatted == "Some warning!" && event.level?.name == "WARNING"
53+
}
54+
55+
// Verify we received the RuntimeException
56+
testHelper.ensureErrorReceived { event ->
57+
event.exceptions?.any { ex -> ex.type == "RuntimeException" && ex.value == "Some error!" } ==
58+
true
59+
}
60+
61+
// Verify we received the detailed event with fingerprint
62+
testHelper.ensureErrorReceived { event ->
63+
event.message?.message == "Detailed event" &&
64+
event.fingerprints?.contains("NewClientDebug") == true &&
65+
event.level?.name == "DEBUG"
66+
}
67+
68+
// Verify we received transaction events
69+
testHelper.ensureTransactionReceived { transaction, _ ->
70+
transaction.transaction == "transaction name" &&
71+
transaction.spans?.any { span -> span.op == "child" } == true
72+
}
73+
74+
// Verify we received the loop messages (should be 10 of them)
75+
var loopMessageCount = 0
76+
try {
77+
for (i in 0..9) {
78+
testHelper.ensureErrorReceived { event ->
79+
val matches =
80+
event.message?.message?.contains("items we'll wait to flush to Sentry!") == true
81+
if (matches) loopMessageCount++
82+
matches
83+
}
84+
}
85+
} catch (e: Exception) {
86+
// Some loop messages might be missing, but we should have at least some
87+
}
88+
89+
assertTrue(
90+
"Should receive at least 5 loop messages, got $loopMessageCount",
91+
loopMessageCount >= 5,
92+
)
93+
94+
// Verify we have breadcrumbs
95+
testHelper.ensureErrorReceived { event ->
96+
event.breadcrumbs?.any { breadcrumb ->
97+
breadcrumb.message?.contains("Processed by") == true
98+
} == true
99+
}
100+
}
101+
}

sentry-samples/sentry-samples-console/src/main/java/io/sentry/samples/console/Main.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ public static void main(String[] args) throws InterruptedException {
6666
// Enable SDK logging with Debug level
6767
options.setDebug(true);
6868
// To change the verbosity, use:
69-
// options.setDiagnosticLevel(SentryLevel.ERROR);
7069
// By default it's DEBUG.
70+
// options.setDiagnosticLevel(SentryLevel.ERROR);
7171
// A good option to have SDK debug log in prod is to use only level ERROR here.
7272

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

8484
// Determine traces sample rate based on the sampling context
85-
// options.setTracesSampler(
86-
// context -> {
87-
// // only 10% of transactions with "/product" prefix will be collected
88-
// if (!context.getTransactionContext().getName().startsWith("/products")) {
89-
// return 0.1;
90-
// } else {
91-
// return 0.5;
92-
// }
93-
// });
85+
// options.setTracesSampler(
86+
// context -> {
87+
// // only 10% of transactions with "/product" prefix will be collected
88+
// if (!context.getTransactionContext().getName().startsWith("/products"))
89+
// {
90+
// return 0.1;
91+
// } else {
92+
// return 0.5;
93+
// }
94+
// });
9495
});
9596

9697
Sentry.addBreadcrumb(

sentry-system-test-support/api/sentry-system-test-support.api

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,7 +576,8 @@ public final class io/sentry/systemtest/util/TestHelper {
576576
public final fun getJsonSerializer ()Lio/sentry/JsonSerializer;
577577
public final fun getRestClient ()Lio/sentry/systemtest/util/RestTestClient;
578578
public final fun getSentryClient ()Lio/sentry/systemtest/util/SentryMockServerClient;
579-
public final fun launch (Ljava/io/File;Ljava/util/Map;)Ljava/lang/Process;
579+
public final fun launch (Ljava/io/File;Ljava/util/Map;Z)Ljava/lang/Process;
580+
public static synthetic fun launch$default (Lio/sentry/systemtest/util/TestHelper;Ljava/io/File;Ljava/util/Map;ZILjava/lang/Object;)Ljava/lang/Process;
580581
public final fun logObject (Ljava/lang/Object;)V
581582
public final fun reset ()V
582583
public final fun setEnvelopeCounts (Lio/sentry/systemtest/util/EnvelopeCounts;)V

0 commit comments

Comments
 (0)