diff --git a/.github/workflows/spring-boot-2-matrix.yml b/.github/workflows/spring-boot-2-matrix.yml index 38aaacec27a..a076e0d5e63 100644 --- a/.github/workflows/spring-boot-2-matrix.yml +++ b/.github/workflows/spring-boot-2-matrix.yml @@ -62,8 +62,16 @@ jobs: - name: Update Spring Boot 2.x version run: | - sed -i 's/^springboot2=.*/springboot2=${{ matrix.springboot-version }}/' gradle/libs.versions.toml - echo "Updated Spring Boot 2.x version to ${{ matrix.springboot-version }}" + springboot_version="${{ matrix.springboot-version }}" + if [[ "$springboot_version" =~ ^2\.[0-3]\. ]]; then + springboot_version="${springboot_version}.RELEASE" + fi + if [[ ! "$springboot_version" =~ ^2\.7\. ]]; then + echo "ORG_GRADLE_PROJECT_excludeGraphql=true" >> "$GITHUB_ENV" + echo "ORG_GRADLE_PROJECT_excludeKafka=true" >> "$GITHUB_ENV" + fi + sed -i 's/^\(springboot2[[:space:]]*=[[:space:]]*\)".*"/\1"'"$springboot_version"'"/' gradle/libs.versions.toml + echo "Updated Spring Boot 2.x version to $springboot_version" - name: Exclude android modules from build run: | @@ -133,6 +141,7 @@ jobs: --build "true" - name: Test sentry-samples-spring-boot-opentelemetry-noagent + if: ${{ !startsWith(matrix.springboot-version, '2.1.') && !startsWith(matrix.springboot-version, '2.2.') }} run: | python3 test/system-test-runner.py test \ --module "sentry-samples-spring-boot-opentelemetry-noagent" \ diff --git a/.github/workflows/spring-boot-3-matrix.yml b/.github/workflows/spring-boot-3-matrix.yml index 629535e282d..930b1f43386 100644 --- a/.github/workflows/spring-boot-3-matrix.yml +++ b/.github/workflows/spring-boot-3-matrix.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - springboot-version: [ '3.0.0', '3.2.12', '3.3.13', '3.4.13', '3.5.13' ] + springboot-version: [ '3.2.12', '3.3.13', '3.4.13', '3.5.13' ] name: Spring Boot ${{ matrix.springboot-version }} env: @@ -62,7 +62,7 @@ jobs: - name: Update Spring Boot 3.x version run: | - sed -i 's/^springboot3=.*/springboot3=${{ matrix.springboot-version }}/' gradle/libs.versions.toml + sed -i 's/^\(springboot3[[:space:]]*=[[:space:]]*\)".*"/\1"${{ matrix.springboot-version }}"/' gradle/libs.versions.toml echo "Updated Spring Boot 3.x version to ${{ matrix.springboot-version }}" - name: Exclude android modules from build diff --git a/.github/workflows/spring-boot-4-matrix.yml b/.github/workflows/spring-boot-4-matrix.yml index bbd4f986d96..aa4671cdab1 100644 --- a/.github/workflows/spring-boot-4-matrix.yml +++ b/.github/workflows/spring-boot-4-matrix.yml @@ -62,7 +62,7 @@ jobs: - name: Update Spring Boot 4.x version run: | - sed -i 's/^springboot4=.*/springboot4=${{ matrix.springboot-version }}/' gradle/libs.versions.toml + sed -i 's/^\(springboot4[[:space:]]*=[[:space:]]*\)".*"/\1"${{ matrix.springboot-version }}"/' gradle/libs.versions.toml echo "Updated Spring Boot 4.x version to ${{ matrix.springboot-version }}" - name: Exclude android modules from build diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ab39c981b44..10f5e665b81 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ retrofit = "2.9.0" slf4j = "1.7.30" springboot2 = "2.7.18" springboot3 = "3.5.0" +springboot3Plugin = "3.5.0" springboot4 = "4.0.0" # Android targetSdk = "36" @@ -61,7 +62,7 @@ detekt = { id = "io.gitlab.arturbosch.detekt", version = "1.23.8" } jacoco-android = { id = "com.mxalbert.gradle.jacoco-android", version = "0.2.0" } kover = { id = "org.jetbrains.kotlinx.kover", version = "0.7.3" } vanniktech-maven-publish = { id = "com.vanniktech.maven.publish", version = "0.30.0" } -springboot3 = { id = "org.springframework.boot", version.ref = "springboot3" } +springboot3 = { id = "org.springframework.boot", version.ref = "springboot3Plugin" } springboot4 = { id = "org.springframework.boot", version.ref = "springboot4" } spring-dependency-management = { id = "io.spring.dependency-management", version = "1.1.7" } gretty = { id = "org.gretty", version = "4.0.0" } @@ -160,6 +161,7 @@ slf4j2-api = { module = "org.slf4j:slf4j-api", version = "2.0.5" } spotlessLib = { module = "com.diffplug.spotless:com.diffplug.spotless.gradle.plugin", version.ref = "spotless"} springboot2-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "springboot2" } springboot-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot2" } +spring-graphql = { module = "org.springframework.graphql:spring-graphql", version = "1.0.6" } springboot-starter-graphql = { module = "org.springframework.boot:spring-boot-starter-graphql", version.ref = "springboot2" } springboot-starter-quartz = { module = "org.springframework.boot:spring-boot-starter-quartz", version.ref = "springboot2" } springboot-starter-test = { module = "org.springframework.boot:spring-boot-starter-test", version.ref = "springboot2" } diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts index ed0af32b031..7137a71d8b3 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry-noagent/build.gradle.kts @@ -18,6 +18,13 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") + mavenBom(libs.otel.instrumentation.bom.get().toString()) + } +} + // Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version extra["kotlin-coroutines.version"] = "1.9.0" @@ -79,8 +86,6 @@ dependencies { testImplementation("ch.qos.logback:logback-core:1.5.16") } -dependencyManagement { imports { mavenBom(libs.otel.instrumentation.bom.get().toString()) } } - configure { test { java.srcDir("src/test/java") } } tasks.register("systemTest").configure { diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts index d3d66c469b7..1bf1fca2058 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta-opentelemetry/build.gradle.kts @@ -19,6 +19,12 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") + } +} + // Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version extra["kotlin-coroutines.version"] = "1.9.0" diff --git a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts index ae3ef70ad70..2bb95b9b413 100644 --- a/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-jakarta/build.gradle.kts @@ -18,6 +18,12 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") + } +} + // Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version extra["kotlin-coroutines.version"] = "1.9.0" diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts index f1665f513d1..63b97a46e12 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry-noagent/build.gradle.kts @@ -15,25 +15,37 @@ group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } +fun springBoot2SupportsOptionalIntegrations(): Boolean { + val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") + val parts = version.split(".").map { it.toIntOrNull() ?: 0 } + val major = parts.getOrElse(0) { 0 } + val minor = parts.getOrElse(1) { 0 } + return major > 2 || (major == 2 && minor >= 7) +} + +val includeGraphql = + !project.hasProperty("excludeGraphql") && springBoot2SupportsOptionalIntegrations() +val includeKafka = !project.hasProperty("excludeKafka") && springBoot2SupportsOptionalIntegrations() + configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } tasks.withType().configureEach { - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } tasks.withType().configureEach { kotlin { compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } } @@ -43,7 +55,9 @@ dependencies { implementation(libs.springboot.starter) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.aop) - implementation(libs.springboot.starter.graphql) + if (includeGraphql) { + implementation(libs.springboot.starter.graphql) + } implementation(libs.springboot.starter.jdbc) implementation(libs.springboot.starter.quartz) implementation(libs.springboot.starter.security) @@ -55,14 +69,17 @@ dependencies { implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarter) implementation(projects.sentryLogback) - implementation(projects.sentryGraphql) + if (includeGraphql) { + implementation(projects.sentryGraphql) + } implementation(projects.sentryQuartz) implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentlessSpring) implementation(projects.sentryAsyncProfiler) - // kafka - implementation(libs.spring.kafka2) - implementation(projects.sentryKafka) + if (includeKafka) { + implementation(libs.spring.kafka2) + implementation(projects.sentryKafka) + } // database query tracing implementation(projects.sentryJdbc) @@ -103,7 +120,19 @@ tasks.jar { tasks.startScripts { dependsOn(tasks.shadowJar) } -configure { test { java.srcDir("src/test/java") } } +configure { + main { + if (!includeGraphql) { + java.exclude("**/graphql/**") + resources.exclude("graphql/**") + } + if (!includeKafka) { + java.exclude("**/queues/kafka/**") + resources.exclude("application-kafka.properties") + } + } + test { java.srcDir("src/test/java") } +} tasks.register("systemTest").configure { group = "verification" @@ -121,7 +150,15 @@ tasks.register("systemTest").configure { minHeapSize = "128m" maxHeapSize = "1g" - filter { includeTestsMatching("io.sentry.systemtest*") } + filter { + includeTestsMatching("io.sentry.systemtest*") + if (!includeGraphql) { + excludeTestsMatching("io.sentry.systemtest.Graphql*") + } + if (!includeKafka) { + excludeTestsMatching("io.sentry.systemtest.Kafka*") + } + } } tasks.named("test").configure { diff --git a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts index 7c84875ca07..a1e260cc307 100644 --- a/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-opentelemetry/build.gradle.kts @@ -15,22 +15,34 @@ group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } +fun springBoot2SupportsOptionalIntegrations(): Boolean { + val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") + val parts = version.split(".").map { it.toIntOrNull() ?: 0 } + val major = parts.getOrElse(0) { 0 } + val minor = parts.getOrElse(1) { 0 } + return major > 2 || (major == 2 && minor >= 7) +} + +val includeGraphql = + !project.hasProperty("excludeGraphql") && springBoot2SupportsOptionalIntegrations() +val includeKafka = !project.hasProperty("excludeKafka") && springBoot2SupportsOptionalIntegrations() + configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } tasks.withType().configureEach { - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 kotlin { compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } } @@ -39,7 +51,9 @@ dependencies { implementation(libs.springboot.starter) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.aop) - implementation(libs.springboot.starter.graphql) + if (includeGraphql) { + implementation(libs.springboot.starter.graphql) + } implementation(libs.springboot.starter.jdbc) implementation(libs.springboot.starter.quartz) implementation(libs.springboot.starter.security) @@ -51,14 +65,17 @@ dependencies { implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarter) implementation(projects.sentryLogback) - implementation(projects.sentryGraphql) + if (includeGraphql) { + implementation(projects.sentryGraphql) + } implementation(projects.sentryQuartz) implementation(projects.sentryAsyncProfiler) implementation(libs.otel) - // kafka - implementation(libs.spring.kafka2) - implementation(projects.sentryKafka) + if (includeKafka) { + implementation(libs.spring.kafka2) + implementation(projects.sentryKafka) + } // database query tracing implementation(projects.sentryJdbc) @@ -99,7 +116,19 @@ tasks.jar { tasks.startScripts { dependsOn(tasks.shadowJar) } -configure { test { java.srcDir("src/test/java") } } +configure { + main { + if (!includeGraphql) { + java.exclude("**/graphql/**") + resources.exclude("graphql/**") + } + if (!includeKafka) { + java.exclude("**/queues/kafka/**") + resources.exclude("application-kafka.properties") + } + } + test { java.srcDir("src/test/java") } +} tasks.register("bootRunWithAgent").configure { group = "application" @@ -141,7 +170,15 @@ tasks.register("systemTest").configure { minHeapSize = "128m" maxHeapSize = "1g" - filter { includeTestsMatching("io.sentry.systemtest*") } + filter { + includeTestsMatching("io.sentry.systemtest*") + if (!includeGraphql) { + excludeTestsMatching("io.sentry.systemtest.Graphql*") + } + if (!includeKafka) { + excludeTestsMatching("io.sentry.systemtest.Kafka*") + } + } } tasks.named("test").configure { diff --git a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts index d5b04543576..76e29fb87c9 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux-jakarta/build.gradle.kts @@ -18,6 +18,12 @@ java.targetCompatibility = JavaVersion.VERSION_17 repositories { mavenCentral() } +dependencyManagement { + imports { + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") + } +} + // Apollo 4.x requires coroutines 1.9.0+, override Spring Boot's managed version extra["kotlin-coroutines.version"] = "1.9.0" diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts index b10b30737d8..08a98ae1665 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot-webflux/build.gradle.kts @@ -15,22 +15,36 @@ group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } +fun springBoot2SupportsGraphql(): Boolean { + val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") + val parts = version.split(".").map { it.toIntOrNull() ?: 0 } + val major = parts.getOrElse(0) { 0 } + val minor = parts.getOrElse(1) { 0 } + return major > 2 || (major == 2 && minor >= 7) +} + +val includeGraphql = !project.hasProperty("excludeGraphql") && springBoot2SupportsGraphql() + dependencies { implementation(platform(libs.springboot2.bom)) implementation(libs.springboot.starter.actuator) - implementation(libs.springboot.starter.graphql) + if (includeGraphql) { + implementation(libs.springboot.starter.graphql) + } implementation(libs.springboot.starter.webflux) implementation(Config.Libs.kotlinReflect) implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarter) implementation(projects.sentryLogback) - implementation(projects.sentryGraphql) + if (includeGraphql) { + implementation(projects.sentryGraphql) + } implementation(projects.sentryAsyncProfiler) testImplementation(kotlin(Config.kotlinStdLib)) @@ -68,12 +82,20 @@ tasks.jar { tasks.startScripts { dependsOn(tasks.shadowJar) } -configure { test { java.srcDir("src/test/java") } } +configure { + main { + if (!includeGraphql) { + java.exclude("**/graphql/**") + resources.exclude("graphql/**") + } + } + test { java.srcDir("src/test/java") } +} tasks.withType().configureEach { kotlin { compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } } @@ -93,7 +115,12 @@ tasks.register("systemTest").configure { minHeapSize = "128m" maxHeapSize = "1g" - filter { includeTestsMatching("io.sentry.systemtest*") } + filter { + includeTestsMatching("io.sentry.systemtest*") + if (!includeGraphql) { + excludeTestsMatching("io.sentry.systemtest.Graphql*") + } + } } tasks.named("test").configure { diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java index cd69d854006..4bd6bb77bfb 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/DistributedTracingController.java @@ -1,6 +1,7 @@ package io.sentry.samples.spring.boot; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; @@ -17,6 +18,10 @@ @RequestMapping("/tracing/") public class DistributedTracingController { private static final Logger LOGGER = LoggerFactory.getLogger(DistributedTracingController.class); + private static final String BASIC_AUTH = + "Basic " + + Base64.getEncoder().encodeToString("user:password".getBytes(StandardCharsets.UTF_8)); + private final WebClient webClient; public DistributedTracingController(WebClient webClient) { @@ -28,9 +33,7 @@ Mono person(@PathVariable Long id) { return webClient .get() .uri("http://localhost:8080/person/{id}", id) - .header( - HttpHeaders.AUTHORIZATION, - "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .header(HttpHeaders.AUTHORIZATION, BASIC_AUTH) .retrieve() .bodyToMono(Person.class) .map(response -> response); @@ -41,9 +44,7 @@ Mono create(@RequestBody Person person) { return webClient .post() .uri("http://localhost:8080/person/") - .header( - HttpHeaders.AUTHORIZATION, - "Basic " + HttpHeaders.encodeBasicAuth("user", "password", Charset.defaultCharset())) + .header(HttpHeaders.AUTHORIZATION, BASIC_AUTH) .body(Mono.just(person), Person.class) .retrieve() .bodyToMono(Person.class) diff --git a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java index ed7422d9d0b..4a9ae98a447 100644 --- a/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java +++ b/sentry-samples/sentry-samples-spring-boot-webflux/src/main/java/io/sentry/samples/spring/boot/PersonService.java @@ -4,14 +4,12 @@ import java.time.Duration; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; @Service public class PersonService { Mono create(Person person) { return Mono.delay(Duration.ofMillis(100)) - .publishOn(Schedulers.boundedElastic()) .doOnNext(__ -> Sentry.captureMessage("Creating person")) .map(__ -> person); } diff --git a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts index cc535c725e1..a286583b66a 100644 --- a/sentry-samples/sentry-samples-spring-boot/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-boot/build.gradle.kts @@ -15,21 +15,33 @@ group = "io.sentry.sample.spring-boot" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_17 +java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_17 +java.targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() } +fun springBoot2SupportsOptionalIntegrations(): Boolean { + val version = libs.versions.springboot2.get().removeSuffix(".RELEASE") + val parts = version.split(".").map { it.toIntOrNull() ?: 0 } + val major = parts.getOrElse(0) { 0 } + val minor = parts.getOrElse(1) { 0 } + return major > 2 || (major == 2 && minor >= 7) +} + +val includeGraphql = + !project.hasProperty("excludeGraphql") && springBoot2SupportsOptionalIntegrations() +val includeKafka = !project.hasProperty("excludeKafka") && springBoot2SupportsOptionalIntegrations() + configure { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } tasks.withType().configureEach { kotlin { compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict") - compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11 } } @@ -38,7 +50,9 @@ dependencies { implementation(libs.springboot.starter) implementation(libs.springboot.starter.actuator) implementation(libs.springboot.starter.aop) - implementation(libs.springboot.starter.graphql) + if (includeGraphql) { + implementation(libs.springboot.starter.graphql) + } implementation(libs.springboot.starter.jdbc) implementation(libs.springboot.starter.quartz) implementation(libs.springboot.starter.security) @@ -48,15 +62,18 @@ dependencies { implementation(libs.springboot.starter.websocket) implementation(libs.caffeine) - // kafka - implementation(libs.spring.kafka2) - implementation(projects.sentryKafka) + if (includeKafka) { + implementation(libs.spring.kafka2) + implementation(projects.sentryKafka) + } implementation(Config.Libs.aspectj) implementation(Config.Libs.kotlinReflect) implementation(kotlin(Config.kotlinStdLib, KotlinCompilerVersion.VERSION)) implementation(projects.sentrySpringBootStarter) implementation(projects.sentryLogback) - implementation(projects.sentryGraphql) + if (includeGraphql) { + implementation(projects.sentryGraphql) + } implementation(projects.sentryQuartz) implementation(projects.sentryAsyncProfiler) @@ -102,7 +119,19 @@ tasks.jar { tasks.startScripts { dependsOn(tasks.shadowJar) } -configure { test { java.srcDir("src/test/java") } } +configure { + main { + if (!includeGraphql) { + java.exclude("**/graphql/**") + resources.exclude("graphql/**") + } + if (!includeKafka) { + java.exclude("**/queues/kafka/**") + resources.exclude("application-kafka.properties") + } + } + test { java.srcDir("src/test/java") } +} tasks.register("systemTest").configure { group = "verification" @@ -120,7 +149,15 @@ tasks.register("systemTest").configure { minHeapSize = "128m" maxHeapSize = "1g" - filter { includeTestsMatching("io.sentry.systemtest*") } + filter { + includeTestsMatching("io.sentry.systemtest*") + if (!includeGraphql) { + excludeTestsMatching("io.sentry.systemtest.Graphql*") + } + if (!includeKafka) { + excludeTestsMatching("io.sentry.systemtest.Kafka*") + } + } } tasks.named("test").configure { diff --git a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts index 319431e71d2..c8ef0a35f19 100644 --- a/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring-jakarta/build.gradle.kts @@ -1,9 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.springframework.boot.gradle.plugin.SpringBootPlugin plugins { application - alias(libs.plugins.springboot3) apply false alias(libs.plugins.spring.dependency.management) alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.spring) @@ -31,8 +29,9 @@ extra["kotlin-coroutines.version"] = "1.9.0" dependencyManagement { imports { - mavenBom(SpringBootPlugin.BOM_COORDINATES) + mavenBom("org.springframework.boot:spring-boot-dependencies:${libs.versions.springboot3.get()}") mavenBom(libs.kotlin.bom.get().toString()) + mavenBom(libs.jackson.bom.get().toString()) } } @@ -57,7 +56,7 @@ dependencies { testImplementation(projects.sentrySystemTestSupport) testImplementation(libs.kotlin.test.junit) - testImplementation(libs.springboot.starter.test) { + testImplementation(libs.springboot3.starter.test) { exclude(group = "org.junit.vintage", module = "junit-vintage-engine") } } diff --git a/sentry-samples/sentry-samples-spring/build.gradle.kts b/sentry-samples/sentry-samples-spring/build.gradle.kts index 446baf3a696..4041bde40b4 100644 --- a/sentry-samples/sentry-samples-spring/build.gradle.kts +++ b/sentry-samples/sentry-samples-spring/build.gradle.kts @@ -33,6 +33,7 @@ dependencyManagement { mavenBom(libs.springboot2.bom.get().toString()) mavenBom(libs.kotlin.bom.get().toString()) mavenBom(libs.jackson.bom.get().toString()) + mavenBom(libs.okhttp.bom.get().toString()) } } diff --git a/sentry-spring-boot/build.gradle.kts b/sentry-spring-boot/build.gradle.kts index 74f5d7c87bb..0f48809a0a7 100644 --- a/sentry-spring-boot/build.gradle.kts +++ b/sentry-spring-boot/build.gradle.kts @@ -35,7 +35,7 @@ dependencies { compileOnly(libs.servlet.api) compileOnly(libs.springboot.starter) compileOnly(libs.springboot.starter.aop) - compileOnly(libs.springboot.starter.graphql) + compileOnly(libs.spring.graphql) compileOnly(libs.springboot.starter.quartz) compileOnly(libs.springboot.starter.security) compileOnly(libs.spring.kafka2) diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java index c7d5a892e9f..f89f5c5bb31 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java @@ -1,7 +1,6 @@ package io.sentry.spring.boot; import com.jakewharton.nopen.annotation.Open; -import graphql.GraphQLError; import io.sentry.EventProcessor; import io.sentry.IScopes; import io.sentry.ISpanFactory; @@ -12,7 +11,6 @@ import io.sentry.Sentry; import io.sentry.SentryIntegrationPackageStorage; import io.sentry.SentryOptions; -import io.sentry.graphql.SentryGraphqlExceptionHandler; import io.sentry.protocol.SdkVersion; import io.sentry.quartz.SentryJobListener; import io.sentry.spring.ContextTagsEventProcessor; @@ -75,7 +73,6 @@ import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; -import org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter; import org.springframework.scheduling.quartz.SchedulerFactoryBean; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.client.RestTemplate; @@ -203,11 +200,12 @@ static class ContextTagsEventProcessorConfiguration { @Configuration(proxyBeanMethods = false) @Import(SentryGraphqlAutoConfiguration.class) @Open - @ConditionalOnClass({ - SentryGraphqlExceptionHandler.class, - DataFetcherExceptionResolverAdapter.class, - GraphQLError.class - }) + @ConditionalOnClass( + name = { + "io.sentry.graphql.SentryGraphqlExceptionHandler", + "org.springframework.graphql.execution.DataFetcherExceptionResolverAdapter", + "graphql.GraphQLError" + }) static class GraphqlConfiguration {} @Configuration(proxyBeanMethods = false) diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentrySpringVersionChecker.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentrySpringVersionChecker.java index 1cbcb4f090c..2da1a3dd8d9 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentrySpringVersionChecker.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentrySpringVersionChecker.java @@ -14,7 +14,8 @@ final class SentrySpringVersionChecker @Override public void onApplicationEvent(ApplicationContextInitializedEvent event) { - if (!SpringBootVersion.getVersion().startsWith("2")) { + String springBootVersion = SpringBootVersion.getVersion(); + if (springBootVersion != null && !springBootVersion.startsWith("2")) { logger.warn("############################### WARNING ###############################"); logger.warn("## ##"); logger.warn("## !Incompatible Spring Boot Version detected! ##"); diff --git a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java index e7f6a444b87..db09180b1c8 100644 --- a/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java +++ b/sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryWebfluxAutoConfiguration.java @@ -6,6 +6,7 @@ import io.sentry.spring.webflux.SentryScheduleHook; import io.sentry.spring.webflux.SentryWebExceptionHandler; import io.sentry.spring.webflux.SentryWebFilter; +import java.util.function.Function; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.springframework.boot.ApplicationRunner; @@ -32,10 +33,21 @@ public class SentryWebfluxAutoConfiguration { @Bean public @NotNull ApplicationRunner sentryScheduleHookApplicationRunner() { return args -> { - Schedulers.onScheduleHook("sentry", new SentryScheduleHook()); + if (hasOnScheduleHook()) { + Schedulers.onScheduleHook("sentry", new SentryScheduleHook()); + } }; } + private static boolean hasOnScheduleHook() { + try { + Schedulers.class.getMethod("onScheduleHook", String.class, Function.class); + return true; + } catch (NoSuchMethodException ignored) { + return false; + } + } + /** Configures a filter that sets up Sentry {@link IScope} for each request. */ @Bean @Order(SENTRY_SPRING_FILTER_PRECEDENCE) diff --git a/sentry-spring/build.gradle.kts b/sentry-spring/build.gradle.kts index c4c75cb5f07..6b75b8d925a 100644 --- a/sentry-spring/build.gradle.kts +++ b/sentry-spring/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { compileOnly(libs.otel) compileOnly(libs.servlet.api) compileOnly(libs.slf4j.api) - compileOnly(libs.springboot.starter.graphql) + compileOnly(libs.spring.graphql) compileOnly(libs.springboot.starter.quartz) compileOnly(libs.spring.kafka2) compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization) diff --git a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java index 03333d95417..5f7cb8bab17 100644 --- a/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java +++ b/sentry-spring/src/main/java/io/sentry/spring/webflux/SentryWebFilter.java @@ -75,38 +75,39 @@ isTracingEnabled && shouldTraceRequest(requestScopes, request) ? startTransaction(requestScopes, request, transactionContext) : null; - return webFilterChain - .filter(serverWebExchange) - .doFinally( - __ -> { - if (transaction != null) { - finishTransaction(serverWebExchange, transaction); - } - Sentry.setCurrentScopes(NoOpScopes.getInstance()); - }) - .doOnError( - e -> { - if (transaction != null) { - transaction.setStatus(SpanStatus.INTERNAL_ERROR); - transaction.setThrowable(e); - } - }) - .doFirst( - () -> { - serverWebExchange.getAttributes().put(SENTRY_SCOPES_KEY, requestScopes); - Sentry.setCurrentScopes(requestScopes); - final ServerHttpResponse response = serverWebExchange.getResponse(); - - final Hint hint = new Hint(); - hint.set(WEBFLUX_FILTER_REQUEST, request); - hint.set(WEBFLUX_FILTER_RESPONSE, response); - final String methodName = - request.getMethod() != null ? request.getMethod().name() : "unknown"; - requestScopes.addBreadcrumb( - Breadcrumb.http(request.getURI().toString(), methodName), hint); - requestScopes.configureScope( - scope -> scope.setRequest(sentryRequestResolver.resolveSentryRequest(request))); - }); + return Mono.defer( + () -> { + serverWebExchange.getAttributes().put(SENTRY_SCOPES_KEY, requestScopes); + Sentry.setCurrentScopes(requestScopes); + final ServerHttpResponse response = serverWebExchange.getResponse(); + + final Hint hint = new Hint(); + hint.set(WEBFLUX_FILTER_REQUEST, request); + hint.set(WEBFLUX_FILTER_RESPONSE, response); + final String methodName = + request.getMethod() != null ? request.getMethod().name() : "unknown"; + requestScopes.addBreadcrumb( + Breadcrumb.http(request.getURI().toString(), methodName), hint); + requestScopes.configureScope( + scope -> scope.setRequest(sentryRequestResolver.resolveSentryRequest(request))); + + return webFilterChain + .filter(serverWebExchange) + .doFinally( + __ -> { + if (transaction != null) { + finishTransaction(serverWebExchange, transaction); + } + Sentry.setCurrentScopes(NoOpScopes.getInstance()); + }) + .doOnError( + e -> { + if (transaction != null) { + transaction.setStatus(SpanStatus.INTERNAL_ERROR); + transaction.setThrowable(e); + } + }); + }); } private boolean isIgnored(final @NotNull IScopes scopes) { @@ -153,7 +154,8 @@ private void finishTransaction(ServerWebExchange exchange, ITransaction transact } final @Nullable ServerHttpResponse response = exchange.getResponse(); if (response != null) { - final @Nullable Integer rawStatusCode = response.getRawStatusCode(); + final @Nullable Integer rawStatusCode = + response.getStatusCode() != null ? response.getStatusCode().value() : null; if (rawStatusCode != null) { transaction .getContexts() diff --git a/test/system-test-runner.py b/test/system-test-runner.py index 784448715e9..7dd7530c8bd 100644 --- a/test/system-test-runner.py +++ b/test/system-test-runner.py @@ -224,11 +224,14 @@ def kill_process(self, pid: int, name: str) -> None: except (OSError, ProcessLookupError): print(f"Process {pid} was already dead") + def exclude_kafka(self) -> bool: + return os.environ.get("ORG_GRADLE_PROJECT_excludeKafka") == "true" + def module_requires_kafka(self, sample_module: str) -> bool: - return sample_module in KAFKA_BROKER_REQUIRED_MODULES + return not self.exclude_kafka() and sample_module in KAFKA_BROKER_REQUIRED_MODULES def module_requires_kafka_profile(self, sample_module: str) -> bool: - return sample_module in KAFKA_PROFILE_REQUIRED_MODULES + return not self.exclude_kafka() and sample_module in KAFKA_PROFILE_REQUIRED_MODULES def wait_for_port(self, host: str, port: int, max_attempts: int = 20) -> bool: for _ in range(max_attempts):