diff --git a/.cursor/rules/e2e_tests.mdc b/.cursor/rules/e2e_tests.mdc index 3603794cd5c..3de11557f87 100644 --- a/.cursor/rules/e2e_tests.mdc +++ b/.cursor/rules/e2e_tests.mdc @@ -83,8 +83,9 @@ it. Gradle runs the two in parallel. customizes, and that the OpenTelemetry starter instruments, so it adds or replaces trace headers on the requests a test sends. Build one outside the context. - Sentry exports OpenTelemetry spans in batches every 5 seconds. In-process, flush the tracer - provider from the server's hook. With the agent this isn't possible, so each test waits for a - batch and negative checks wait at least 6 seconds. + provider from the server's hook. With the agent this isn't possible, so set + `OTEL_BSP_SCHEDULE_DELAY` on the app to shorten the batch delay, and make negative checks wait + well past it. - Sentry is global, so a second live Spring context in an OpenTelemetry suite takes it over. Keep one live context per JVM (`@DirtiesContext` when a test needs its own). - The async profiler rejects a traces path with a `.` anywhere in its absolute form. Put the path diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ff6b1f542e..9a8516abb08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Monitor every Spring `@Scheduled` method with Sentry Crons by setting `sentry.monitor-scheduled-tasks=true`, without adding `@SentryCheckIn` to each method. Sentry creates each monitor from the method's `@Scheduled` schedule. Exclude methods with `sentry.exclude-scheduled-tasks` ([#6275](https://github.com/getsentry/sentry-java/pull/6275), [#6276](https://github.com/getsentry/sentry-java/pull/6276), [#6277](https://github.com/getsentry/sentry-java/pull/6277)) - Quartz `SentryJobListener` can send a monitor config derived from the trigger with check-ins, so Sentry creates or updates the monitor from code. Set the `sentry-upsert-monitor-config` job or trigger data key to `"true"` to send the schedule ([#6216](https://github.com/getsentry/sentry-java/pull/6216)) - Send a monitor config derived from Spring `@Scheduled` with `@SentryCheckIn` check-ins, so Sentry creates or updates the monitor from code. Set `@SentryCheckIn(upsertMonitorConfig = true)` to send the schedule ([#6215](https://github.com/getsentry/sentry-java/pull/6215)) +- The OpenTelemetry agent now honors `OTEL_BSP_SCHEDULE_DELAY` (`otel.bsp.schedule.delay`) for how often it sends spans to Sentry, instead of always waiting 5 seconds ([#6388](https://github.com/getsentry/sentry-java/pull/6388)) ### Behavioral Changes diff --git a/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java b/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java index 5f29f0289a2..e11ae4c72b3 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java +++ b/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java @@ -5,11 +5,13 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder; import io.sentry.InitPriority; import io.sentry.Sentry; import io.sentry.SentryOptions; import io.sentry.internal.ManifestVersionReader; import io.sentry.protocol.SdkVersion; +import java.time.Duration; import java.util.HashMap; import java.util.Map; import java.util.logging.Level; @@ -106,7 +108,16 @@ private SdkTracerProviderBuilder configureSdkTracerProvider( return tracerProvider .setSampler(new SentrySampler()) .addSpanProcessor(new OtelSentrySpanProcessor()) - .addSpanProcessor(BatchSpanProcessor.builder(new SentrySpanExporter()).build()); + .addSpanProcessor(createBatchSpanProcessor(config)); + } + + private BatchSpanProcessor createBatchSpanProcessor(ConfigProperties config) { + final BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder(new SentrySpanExporter()); + final @Nullable Duration scheduleDelay = config.getDuration("otel.bsp.schedule.delay"); + if (scheduleDelay != null) { + builder.setScheduleDelay(scheduleDelay); + } + return builder.build(); } private Map getDefaultProperties() { diff --git a/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/AgentApp.kt b/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/AgentApp.kt index 1ee54ed9795..24496ee290a 100644 --- a/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/AgentApp.kt +++ b/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/AgentApp.kt @@ -41,6 +41,8 @@ object AgentApp { "SENTRY_PROFILE_LIFECYCLE" to "TRACE", "SENTRY_PROFILING_TRACES_DIR_PATH" to "${System.getProperty("java.io.tmpdir")}/sentry-integration-test/profiling-traces", + // Tests wait for Sentry's span batches, which are 5 seconds apart by default. + "OTEL_BSP_SCHEDULE_DELAY" to "100ms", "OTEL_TRACES_EXPORTER" to "none", "OTEL_METRICS_EXPORTER" to "none", "OTEL_LOGS_EXPORTER" to "none", diff --git a/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/DistributedTracingIntegrationTest.kt b/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/DistributedTracingIntegrationTest.kt index 353e6d62fd8..ad7025146b6 100644 --- a/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/DistributedTracingIntegrationTest.kt +++ b/sentry-spring-boot-4/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot4/otelagentintegrationtest/DistributedTracingIntegrationTest.kt @@ -47,8 +47,8 @@ class DistributedTracingIntegrationTest : IntegrationTest() { ) assertThat(status).isEqualTo(500) - // Longer than the 5 seconds the app's spans can wait for their batch to be exported. - server.assertNoEnvelope(millis = 6_000) { isForRequest(it, id) } + // Ten times the app's batch delay, so the request's spans would have been exported. + server.assertNoEnvelope(millis = 1_000) { isForRequest(it, id) } } @Test diff --git a/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/AgentApp.kt b/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/AgentApp.kt index c70f8df301a..6864d4d55c9 100644 --- a/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/AgentApp.kt +++ b/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/AgentApp.kt @@ -41,6 +41,8 @@ object AgentApp { "SENTRY_PROFILE_LIFECYCLE" to "TRACE", "SENTRY_PROFILING_TRACES_DIR_PATH" to "${System.getProperty("java.io.tmpdir")}/sentry-integration-test/profiling-traces", + // Tests wait for Sentry's span batches, which are 5 seconds apart by default. + "OTEL_BSP_SCHEDULE_DELAY" to "100ms", "OTEL_TRACES_EXPORTER" to "none", "OTEL_METRICS_EXPORTER" to "none", "OTEL_LOGS_EXPORTER" to "none", diff --git a/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/DistributedTracingIntegrationTest.kt b/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/DistributedTracingIntegrationTest.kt index e83dc383dc3..309fa9b820b 100644 --- a/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/DistributedTracingIntegrationTest.kt +++ b/sentry-spring-boot-jakarta/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/jakarta/otelagentintegrationtest/DistributedTracingIntegrationTest.kt @@ -47,8 +47,8 @@ class DistributedTracingIntegrationTest : IntegrationTest() { ) assertThat(status).isEqualTo(500) - // Longer than the 5 seconds the app's spans can wait for their batch to be exported. - server.assertNoEnvelope(millis = 6_000) { isForRequest(it, id) } + // Ten times the app's batch delay, so the request's spans would have been exported. + server.assertNoEnvelope(millis = 1_000) { isForRequest(it, id) } } @Test diff --git a/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/AgentApp.kt b/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/AgentApp.kt index a6bd0bb35ff..32dd96a86d5 100644 --- a/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/AgentApp.kt +++ b/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/AgentApp.kt @@ -34,6 +34,8 @@ class AgentApp(args: List = emptyList()) : Closeable { "SENTRY_PROFILE_LIFECYCLE" to "TRACE", "SENTRY_PROFILING_TRACES_DIR_PATH" to "${System.getProperty("java.io.tmpdir")}/sentry-integration-test/profiling-traces", + // Tests wait for Sentry's span batches, which are 5 seconds apart by default. + "OTEL_BSP_SCHEDULE_DELAY" to "100ms", "OTEL_TRACES_EXPORTER" to "none", "OTEL_METRICS_EXPORTER" to "none", "OTEL_LOGS_EXPORTER" to "none", diff --git a/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/DistributedTracingIntegrationTest.kt b/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/DistributedTracingIntegrationTest.kt index ad67ae78981..9ce058b9c3f 100644 --- a/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/DistributedTracingIntegrationTest.kt +++ b/sentry-spring-boot/src/integrationTestOtelAgent/kotlin/io/sentry/spring/boot/otelagentintegrationtest/DistributedTracingIntegrationTest.kt @@ -47,8 +47,8 @@ class DistributedTracingIntegrationTest : IntegrationTest() { ) assertThat(status).isEqualTo(500) - // Longer than the 5 seconds the app's spans can wait for their batch to be exported. - server.assertNoEnvelope(millis = 6_000) { isForRequest(it, id) } + // Ten times the app's batch delay, so the request's spans would have been exported. + server.assertNoEnvelope(millis = 1_000) { isForRequest(it, id) } } @Test