Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions .github/workflows/spring-boot-3-matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ jobs:
- name: Build sample artifacts
run: |
./gradlew \
:sentry-samples:sentry-samples-spring-boot-jakarta:bootJar \
:sentry-samples:sentry-samples-spring-boot-jakarta:testClasses \
:sentry-samples:sentry-samples-spring-boot-webflux-jakarta:bootJar \
:sentry-samples:sentry-samples-spring-boot-webflux-jakarta:testClasses \
:sentry-samples:sentry-samples-spring-boot-jakarta-opentelemetry:bootJar \
Expand All @@ -89,13 +87,6 @@ jobs:
:sentry-spring-boot-jakarta:integrationTest \
:sentry-spring-boot-jakarta:integrationTestOtelAgent

- name: Test sentry-samples-spring-boot-jakarta
run: |
python3 test/system-test-runner.py test \
--module "sentry-samples-spring-boot-jakarta" \
--agent false \
--auto-init "true"

- name: Test sentry-samples-spring-boot-webflux-jakarta
run: |
python3 test/system-test-runner.py test \
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/system-tests-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,10 @@ jobs:
strategy:
fail-fast: false
matrix:
sample: [ "sentry-samples-spring-boot-jakarta" ]
sample: [ "sentry-samples-spring-boot" ]
agent: [ "false" ]
agent-auto-init: [ "true" ]
include:
- sample: "sentry-samples-spring-boot"
agent: "false"
agent-auto-init: "true"
- sample: "sentry-samples-spring-boot-opentelemetry-noagent"
agent: "false"
agent-auto-init: "true"
Expand Down
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ spotless = "8.8.0"
springboot2 = "2.7.18"
springboot3 = "3.5.0"
springboot4 = "4.1.0"
springKafka3 = "3.3.5"
sqldelight = "2.3.2"

# Android
Expand Down Expand Up @@ -219,7 +220,8 @@ springboot3-starter-jdbc = { module = "org.springframework.boot:spring-boot-star
springboot3-starter-actuator = { module = "org.springframework.boot:spring-boot-starter-actuator", version.ref = "springboot3" }
springboot3-starter-cache = { module = "org.springframework.boot:spring-boot-starter-cache", version.ref = "springboot3" }
spring-kafka2 = { module = "org.springframework.kafka:spring-kafka", version = "2.8.11" }
spring-kafka3 = { module = "org.springframework.kafka:spring-kafka", version = "3.3.5" }
spring-kafka3 = { module = "org.springframework.kafka:spring-kafka", version.ref = "springKafka3" }
spring-kafka3-test = { module = "org.springframework.kafka:spring-kafka-test", version.ref = "springKafka3" }
spring-kafka4 = { module = "org.springframework.kafka:spring-kafka" }
kafka-clients = { module = "org.apache.kafka:kafka-clients", version = "3.8.1" }
springboot4-otel = { module = "io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter", version.ref = "otelInstrumentation" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ plugins {
alias(libs.plugins.spring.dependency.management)
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.spring)
id("io.sentry.systemtest")
}

group = "io.sentry.sample.spring-boot-jakarta"
Expand Down Expand Up @@ -67,34 +66,4 @@ dependencies {
// database query tracing
implementation(projects.sentryJdbc)
runtimeOnly(libs.hsqldb)

testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(projects.sentry)
testImplementation(projects.sentrySystemTestSupport)
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.slf4j2.api)
testImplementation(libs.springboot3.starter.test) {
exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
}
testImplementation("ch.qos.logback:logback-classic:1.5.16")
testImplementation("ch.qos.logback:logback-core:1.5.16")
}

tasks.register<Test>("systemTest").configure {
group = "verification"
description = "Runs the System tests"

val test = project.extensions.getByType<SourceSetContainer>()["test"]
testClassesDirs = test.output.classesDirs
classpath = test.runtimeClasspath

maxParallelForks = 1

filter { includeTestsMatching("io.sentry.systemtest*") }
}

tasks.named("test").configure {
require(this is Test)

filter { excludeTestsMatching("io.sentry.systemtest.*") }
}

This file was deleted.

This file was deleted.

This file was deleted.

3 changes: 3 additions & 0 deletions sentry-spring-boot-jakarta/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ testing {
implementation(projects.sentryAsyncProfiler)
implementation(projects.sentryGraphql22)
implementation(projects.sentryJdbc)
implementation(projects.sentryKafka)
implementation(projects.sentryLogback)
implementation(projects.sentryOkhttp)
implementation(projects.sentryOpenfeature)
Expand All @@ -200,6 +201,8 @@ testing {
implementation(libs.springboot3.starter.test)
implementation(libs.springboot3.starter.web)
implementation(libs.springboot3.starter.webflux)
implementation(libs.spring.kafka3)
implementation(libs.spring.kafka3.test)
runtimeOnly(libs.hsqldb)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.kafka.test.context.EmbeddedKafka
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.springframework.test.context.junit4.SpringRunner

/**
* Drives [IntegrationTestApplication] over HTTP and asserts what reaches [server].
*
* Subclasses share one application context and one server, so the app starts once per suite.
* Envelopes that arrive late can outlive [server]'s reset, so tests match on values only they
* produce.
* Subclasses share one application context, one server and one Kafka broker, so each starts once
* per suite. Envelopes that arrive late can outlive [server]'s reset, so tests match on values only
* they produce.
*/
@RunWith(SpringRunner::class)
// An in-JVM KRaft broker, so no Docker is needed. As part of the context, it starts before the
// app's Kafka listener connects and stops after it.
@EmbeddedKafka(topics = [KAFKA_TOPIC], bootstrapServersProperty = "spring.kafka.bootstrap-servers")
@SpringBootTest(
classes = [IntegrationTestApplication::class],
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
Expand All @@ -27,12 +31,14 @@ import org.springframework.test.context.junit4.SpringRunner
"sentry.traces-sample-rate=1.0",
"sentry.clients.ok-http-enabled=true",
"sentry.enable-cache-tracing=true",
"sentry.enable-queue-tracing=true",
"sentry.logs.enabled=true",
"sentry.profile-session-sample-rate=1.0",
"sentry.profile-lifecycle=TRACE",
"sentry.profiling-traces-dir-path=\${java.io.tmpdir}/sentry-integration-test/profiling-traces",
"spring.datasource.url=jdbc:p6spy:hsqldb:mem:testdb",
"spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver",
"spring.kafka.consumer.auto-offset-reset=earliest",
],
)
abstract class IntegrationTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.sentry.spring.boot.jakarta.integrationtest

import org.springframework.kafka.annotation.KafkaListener
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController

const val KAFKA_TOPIC = "sentry-topic"

@RestController
@RequestMapping("/kafka")
open class KafkaController(private val kafkaTemplate: KafkaTemplate<String, String>) {
@GetMapping("/produce")
open fun produce(@RequestParam message: String): String {
kafkaTemplate.send(KAFKA_TOPIC, message)
return "Message sent: $message"
}
}

@Component
open class KafkaConsumer {
/** Does nothing with the message; Sentry traces the listener invocation. */
@KafkaListener(topics = [KAFKA_TOPIC], groupId = "sentry-integration-test")
open fun listen(message: String) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package io.sentry.spring.boot.jakarta.integrationtest

import com.google.common.truth.Truth.assertThat
import io.sentry.protocol.SentryTransaction
import io.sentry.test.transaction
import kotlin.test.Test

/** A message sent through Kafka is traced on both sides: where it's published and processed. */
class KafkaIntegrationTest : IntegrationTest() {

@Test
fun `publishing a message traces a queue publish span`() {
val producer = produce("publish-test")

val span = producer.spans.single { it.op == "queue.publish" }
assertThat(span.data).containsEntry("messaging.system", "kafka")
assertThat(span.data).containsEntry("messaging.destination.name", KAFKA_TOPIC)
}

@Test
fun `processing a message continues the producer's trace`() {
val traceId = produce("process-test").contexts.trace!!.traceId

val consumer =
server
.awaitEnvelope {
it.transaction()?.contexts?.trace?.let { trace ->
trace.operation == "queue.process" && trace.traceId == traceId
} == true
}
.transaction()!!
val data = consumer.contexts.trace!!.data
assertThat(data).containsEntry("messaging.system", "kafka")
assertThat(data).containsEntry("messaging.destination.name", KAFKA_TOPIC)
}

/** Sends [message] through the app and returns the transaction of the request that sent it. */
private fun produce(message: String): SentryTransaction {
assertThat(restTemplate.getForObject("/kafka/produce?message=$message", String::class.java))
.isEqualTo("Message sent: $message")
return server
.awaitEnvelope {
it.transaction()?.let { t ->
t.transaction == "GET /kafka/produce" && t.request?.queryString == "message=$message"
} == true
}
.transaction()!!
}
}
Loading
Loading