From 948a01d8531a9936c3e113080450c61a2cd402cd Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 27 Oct 2026 11:42:15 +0100 Subject: [PATCH 1/3] feat(opentelemetry): Honor OTEL_BSP_SCHEDULE_DELAY in the agent The agent built Sentry's BatchSpanProcessor with the OpenTelemetry default, so spans always reached Sentry in 5 second batches, whatever otel.bsp.schedule.delay said. Read it from the agent's config instead. The Boot 4 agent suites set it to 100ms. Each of their tests waited for a 5 second batch, which made them take minutes on CI. Co-Authored-By: Claude Opus 5.5 --- .cursor/rules/e2e_tests.mdc | 6 ++++-- .../SentryAutoConfigurationCustomizerProvider.java | 13 ++++++++++++- .../boot4/otelagentintegrationtest/AgentApp.kt | 2 ++ .../DistributedTracingIntegrationTest.kt | 4 ++-- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.cursor/rules/e2e_tests.mdc b/.cursor/rules/e2e_tests.mdc index 3603794cd5c..793158e8327 100644 --- a/.cursor/rules/e2e_tests.mdc +++ b/.cursor/rules/e2e_tests.mdc @@ -83,8 +83,10 @@ 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. The Boot 2 and Boot 3 agent suites don't set it yet: their tests wait 5 seconds + for a batch and their negative checks wait 6. - 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/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 From 353a8664af96bd9fedeced480e00c8ee44168aaa Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 27 Oct 2026 11:42:53 +0100 Subject: [PATCH 2/3] changelog Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From 676a5d8599b9ac618247e5882faa2440f52f4166 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Fri, 6 Nov 2026 14:28:43 +0100 Subject: [PATCH 3/3] test(spring-boot): Shorten the span batch delay in the Boot 2 and Boot 3 agent suites Co-Authored-By: Claude Opus 5.5 --- .cursor/rules/e2e_tests.mdc | 3 +-- .../spring/boot/jakarta/otelagentintegrationtest/AgentApp.kt | 2 ++ .../DistributedTracingIntegrationTest.kt | 4 ++-- .../sentry/spring/boot/otelagentintegrationtest/AgentApp.kt | 2 ++ .../DistributedTracingIntegrationTest.kt | 4 ++-- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.cursor/rules/e2e_tests.mdc b/.cursor/rules/e2e_tests.mdc index 793158e8327..3de11557f87 100644 --- a/.cursor/rules/e2e_tests.mdc +++ b/.cursor/rules/e2e_tests.mdc @@ -85,8 +85,7 @@ it. Gradle runs the two in parallel. - 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 set `OTEL_BSP_SCHEDULE_DELAY` on the app to shorten the batch delay, and make negative checks wait - well past it. The Boot 2 and Boot 3 agent suites don't set it yet: their tests wait 5 seconds - for a batch and their negative checks wait 6. + 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/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