feat(auto-install): Fail the build on OpenTelemetry version downgrades#1350
feat(auto-install): Fail the build on OpenTelemetry version downgrades#1350adinauer wants to merge 15 commits into
Conversation
Sentry's sentry-opentelemetry-* artifacts are built against specific OpenTelemetry versions. When another dependency management mechanism - most commonly Spring Boot's io.spring.dependency-management - forces OpenTelemetry below the version Sentry's integration requires, running against those downgraded versions can cause ClassNotFoundException / NoSuchMethodError at runtime. Add a JVM-only verifySentryOpenTelemetryVersions task that walks the resolved runtimeClasspath graph and fails the build when an OpenTelemetry module a sentry-opentelemetry-* artifact depends on resolves lower than the requested version. The failure message adapts to the project setup: it suggests importing the Sentry OpenTelemetry BOM via io.spring.dependency-management when that plugin is applied, otherwise a platform() dependency. The check is gated by a cheap declared-dependency lookup so it is skipped (no runtime classpath resolution) for projects that don't use Sentry OpenTelemetry, and can be disabled via sentry.autoInstallation.verifyOpenTelemetryVersions = false. Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
|
…heck Remove comments that restated self-explanatory code (the verifyEnabled property KDoc and two test comments that duplicated their assertions). Co-Authored-By: Claude <noreply@anthropic.com>
The springDependencyManagementApplied and hasSentryOpenTelemetryDependency property names are self-explanatory, and the rationale already lives at the usage sites (buildMessage and register). Co-Authored-By: Claude <noreply@anthropic.com>
| companion object { | ||
| // TODO: finalize the troubleshooting docs URL before release. | ||
| internal const val OPENTELEMETRY_VERSION_MISMATCH_DOCS_URL = | ||
| "https://docs.sentry.io/platforms/java/opentelemetry/troubleshooting/" |
There was a problem hiding this comment.
TODO docs don't exist yet
Co-authored-by: Alexander Dinauer <adinauer@users.noreply.github.com>
runningcode
left a comment
There was a problem hiding this comment.
Thanks for putting this together. Overall looks good. I left some comments, let me know if you'd like to discuss any of these.
| * `NoSuchMethodError` at runtime. We detect that downgrade here and fail fast with actionable | ||
| * guidance instead of letting it blow up at runtime. | ||
| */ | ||
| abstract class SentryOpenTelemetryVersionCheckTask : DefaultTask() { |
There was a problem hiding this comment.
This task is missing either a @DisableCachingByDefault or a @CacheableTask annotation. Check the build failure for details.
In this case since we have no outputs it should probably be @DisableCachingByDefault. Otherwise we can write a simple text file in a task specific output directory and make this task cacheable.
There was a problem hiding this comment.
Skipping the output file for now. We can always add that later if needed.
| import org.gradle.util.GradleVersion | ||
| import org.junit.Test | ||
|
|
||
| class SentryOpenTelemetryVersionCheckTest : |
There was a problem hiding this comment.
Can we add a configuration cache test?
| * `sentry-opentelemetry-*` artifact depends on, the ones whose resolved version is lower than | ||
| * the version Sentry requested. | ||
| */ | ||
| internal fun collectDowngrades(root: ResolvedComponentResult): List<VersionMismatch> { |
There was a problem hiding this comment.
do you think it would be possible to split this pure logic from the gradle task and then unit test that separately?
I'm not saying we need to add more tests, but maybe we can move some of the tests from integration tests to just be plain unit tests which are significantly faster than integration tests that spin up a whole gradle build
| assertTrue { | ||
| "OpenTelemetry was downgraded below the version its integration requires" in result.output | ||
| } | ||
| assertTrue { "io.opentelemetry:opentelemetry-sdk" in result.output } |
There was a problem hiding this comment.
Can we use Google Truth instead of kotlin.test error messages and debugging is a lot easier especially when matching strings.
| private const val OTEL_GROUP = "io.opentelemetry" | ||
| private const val SPRING_DEPENDENCY_MANAGEMENT_PLUGIN_ID = "io.spring.dependency-management" | ||
|
|
||
| internal data class VersionMismatch( |
There was a problem hiding this comment.
nit: In this case we only ever hold downgrades so this might be a better name:
| internal data class VersionMismatch( | |
| internal data class VersionDowngrade( |
| | | ||
| | dependencyManagement { | ||
| | imports { | ||
| | mavenBom("io.sentry:sentry-opentelemetry-bom:<sentryVersion>") |
There was a problem hiding this comment.
would it make sense to fill in from the value in AutoInstallState.sentryVersion ?
and also for 174?
| internal data class VersionMismatch( | ||
| val module: String, | ||
| val requested: String, | ||
| val resolved: String, |
There was a problem hiding this comment.
We can get more information from Gradle's dependency management engine in order to improve the error message.
| val resolved: String, | |
| val resolved: String, | |
| val requestedBy: String, | |
| val reason: String, |
Set requestedBy from component.moduleVersion (which you already know is the Sentry OTel artifact), and pull reason from the selection reason of the selected node:
private fun ResolvedComponentResult.downgradeReason(): String? =
selectionReason.descriptions
.lastOrNull {
it.cause == ComponentSelectionCause.CONSTRAINT ||
it.cause == ComponentSelectionCause.FORCED ||
it.cause == ComponentSelectionCause.CONFLICT_RESOLUTION
}
?.description
Let me know if you need more info here!
| |Sentry detected that OpenTelemetry was downgraded below the version its integration requires. | ||
| | | ||
| |The Sentry OpenTelemetry integration was built against specific OpenTelemetry versions, | ||
| |but the following were downgraded by another dependency management mechanism: |
There was a problem hiding this comment.
You can also add that users can run this command or check a build scan for more details:
./gradlew :app:dependencyInsight --configuration runtimeClasspath --dependency io.opentelemetry:opentelemetry-sdk
There was a problem hiding this comment.
I'm afraid of getting the command wrong and adding even more confusion. Also not fully sure if this works for Spring Boot so I'd rather skip this for now.
Co-authored-by: Nelson Osacky <nelson@osacky.com>
Declare the verification task as non-cacheable because it only validates the resolved dependency graph and has no outputs. Add integration coverage to assert the task stores and reuses Gradle's configuration cache. Co-Authored-By: Claude <noreply@anthropic.com>
| ) | ||
| // Gate compilation so any build that compiles, packages, runs or tests (assemble, bootJar, | ||
| // bootRun, check, ...) fails fast on an OpenTelemetry version mismatch. | ||
| project.tasks.named("classes").configure { it.dependsOn(verifyOpenTelemetryVersionsTask) } |
There was a problem hiding this comment.
Verify hook skips test builds
High Severity
The OpenTelemetry verify task is attached only as a dependency of classes, but the Java plugin’s test task does not depend on classes (it reaches main compilation via compileTestJava → compileJava). Typical ./gradlew test or ./gradlew check runs can therefore compile and execute tests without ever running verifySentryOpenTelemetryVersions, so OTel downgrades may not fail the build despite the stated goal.
Reviewed by Cursor Bugbot for commit e4f8f9b. Configure here.
Remove the outdated placeholder comment now that the troubleshooting docs PR exists. Co-Authored-By: Claude <noreply@anthropic.com>
Rename the internal version check data class to describe the downgrade it represents. Co-Authored-By: Claude <noreply@anthropic.com>
Include the Sentry OpenTelemetry artifact requesting each downgraded module and Gradle's selection reason when available. This gives users more context about which integration was affected and why Gradle resolved the lower OpenTelemetry version. Co-Authored-By: Claude <noreply@anthropic.com>
Use the resolved Sentry OpenTelemetry artifact version in the suggested BOM coordinates instead of a placeholder. This makes the failure message copy-pasteable and matches the version whose OpenTelemetry requirements were checked. Co-Authored-By: Claude <noreply@anthropic.com>
Move OpenTelemetry downgrade detection and message formatting out of the Gradle task so the task only handles wiring and execution. Add focused unit coverage for artifact detection and failure message formatting. Co-Authored-By: Claude <noreply@anthropic.com>
Convert OpenTelemetry version check integration assertions from kotlin.test to Google Truth for clearer string assertion failures. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6a5a723. Configure here.
Clean up the verifyOpenTelemetryVersions property documentation so formatting checks pass and the behavior is described clearly. Co-Authored-By: Claude <noreply@anthropic.com>
runningcode
left a comment
There was a problem hiding this comment.
Looks great! Thanks for the cleanup and adding the configuration cache test.
I realized we should separate integrationTests from unitTests into separate source directories/tasks but that is an already existing organization issue.
| * what the Sentry OpenTelemetry integration requires, failing the build if any were downgraded. | ||
| * Defaults to `true`. | ||
| */ | ||
| val verifyOpenTelemetryVersions: Property<Boolean> = |
There was a problem hiding this comment.
Since this isn't tied to auto install, the option shouldn't live under auto install.
Expose the OpenTelemetry downgrade check directly on the Sentry plugin extension instead of under autoInstallation. This reflects that the check validates dependency resolution independently of auto-installation. Refs #1350 Co-Authored-By: Claude <noreply@anthropic.com>


Adds a JVM-only
verifySentryOpenTelemetryVersionsGradle task that fails the build when the OpenTelemetry versions resolved on the runtime classpath are downgraded below what Sentry'ssentry-opentelemetry-*artifacts require.Why
Sentry's OpenTelemetry artifacts are built against a specific set of OpenTelemetry versions. When another dependency management mechanism — most commonly Spring Boot's
io.spring.dependency-management— forces OpenTelemetry below those versions (which happens silently, without the user declaring any OTel version), running against the mismatched versions throwsClassNotFoundException/NoSuchMethodErrorat runtime. This surfaces the problem at build time with actionable guidance instead.How it works
runtimeClasspathgraph and, for every OpenTelemetry module asentry-opentelemetry-*artifact depends on, compares the requested vs. resolved version. Gradle preserves the originally-requested version on each edge even when a resolution rule (e.g.io.spring.dependency-management) forces it lower, so the downgrade is detectable.io.spring.dependency-management'simports { mavenBom(...) }when that plugin is applied (aplatform()constraint loses to it), otherwise a plainplatform(...)dependency.sentry.autoInstallation.verifyOpenTelemetryVersions = false(defaulttrue).Context
Stacked on #1349 (base branch), which adds
sentry-opentelemetry-bomawareness to auto-install.This replaces an earlier approach that auto-imported the BOM: that only works with Gradle-native BOM support and is silently overridden by
io.spring.dependency-management, so detecting-and-failing is the more honest behavior.Open items from SDK-team review
.../platforms/java/opentelemetry/troubleshooting/) and must resolve to a live page before release.truewill surprise existing Spring Boot users on upgrade. Either keep default-on with a clearly-flagged behavioral-change changelog entry, or ship opt-in first and flip later based on telemetry.