diff --git a/.github/workflows/system-tests-backend.yml b/.github/workflows/system-tests-backend.yml
index 2899af56d0e..eb3721139d1 100644
--- a/.github/workflows/system-tests-backend.yml
+++ b/.github/workflows/system-tests-backend.yml
@@ -34,9 +34,6 @@ jobs:
- sample: "sentry-samples-logback"
agent: "false"
agent-auto-init: "true"
- - sample: "sentry-samples-log4j2"
- agent: "false"
- agent-auto-init: "true"
- sample: "sentry-samples-jul"
agent: "false"
agent-auto-init: "true"
diff --git a/sentry-log4j2/build.gradle.kts b/sentry-log4j2/build.gradle.kts
index 2d2444a2395..92f98d6c7c0 100644
--- a/sentry-log4j2/build.gradle.kts
+++ b/sentry-log4j2/build.gradle.kts
@@ -30,6 +30,7 @@ dependencies {
// tests
testImplementation(projects.sentryTestSupport)
testImplementation(kotlin(Config.kotlinStdLib))
+ testImplementation(libs.google.truth)
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.log4j.api)
testImplementation(libs.log4j.core)
diff --git a/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderIntegrationTest.kt b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderIntegrationTest.kt
new file mode 100644
index 00000000000..76b9e55a5f9
--- /dev/null
+++ b/sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderIntegrationTest.kt
@@ -0,0 +1,77 @@
+package io.sentry.log4j2
+
+import com.google.common.truth.Truth.assertThat
+import io.sentry.Sentry
+import io.sentry.SentryLevel
+import io.sentry.test.MockSentryServer
+import io.sentry.test.event
+import io.sentry.test.hasFlag
+import io.sentry.test.logs
+import kotlin.test.AfterTest
+import kotlin.test.Test
+import org.apache.logging.log4j.ThreadContext
+import org.apache.logging.log4j.core.LoggerContext
+
+class SentryAppenderIntegrationTest {
+ private val server = MockSentryServer()
+
+ // A context of its own, so the other tests' context keeps its configuration.
+ private val loggerContext =
+ LoggerContext(
+ "sentry-appender-integration-test",
+ null,
+ javaClass.getResource("/sentry-appender-integration-test.xml")!!.toURI(),
+ )
+
+ @AfterTest
+ fun teardown() {
+ loggerContext.stop()
+ Sentry.close()
+ server.close()
+ ThreadContext.clearAll()
+ System.clearProperty("integrationTestDsn")
+ System.clearProperty("sentry.logs.enabled")
+ }
+
+ @Test
+ fun `appender configured from XML sends events, breadcrumbs and logs`() {
+ System.setProperty("integrationTestDsn", server.dsn)
+ // The appender has no attribute for logs, so they're enabled the way an app's sentry.properties
+ // would: as external configuration.
+ System.setProperty("sentry.logs.enabled", "true")
+ loggerContext.start()
+ val logger = loggerContext.getLogger(SentryAppenderIntegrationTest::class.java.name)
+
+ logger.debug("Hello Sentry!")
+ ThreadContext.put("userId", "user-1")
+ Sentry.setAttribute("user.type", "admin")
+ Sentry.setAttribute("feature.version", 2)
+ Sentry.setAttribute("debug.enabled", true)
+ Sentry.addFeatureFlag("my-feature-flag", true)
+ logger.info("User has made a purchase of product: {}", 445)
+ logger.warn("Important warning")
+ logger.error("Something went wrong", RuntimeException("Invalid productId=445"))
+ Sentry.flush(5000)
+
+ val warning = server.awaitEnvelope { it.event()?.level == SentryLevel.WARNING }.event()!!
+ assertThat(warning.message!!.message).isEqualTo("Important warning")
+ assertThat(warning.tags).containsEntry("userId", "user-1")
+ assertThat(warning.hasFlag("my-feature-flag", true)).isTrue()
+
+ val error = server.awaitEnvelope { it.event()?.level == SentryLevel.ERROR }.event()!!
+ assertThat(error.message!!.formatted).isEqualTo("Something went wrong")
+ assertThat(error.exceptions!!.map { it.type to it.value })
+ .contains("RuntimeException" to "Invalid productId=445")
+ assertThat(error.breadcrumbs!!.map { it.message to it.level })
+ .containsAtLeast(
+ "Hello Sentry!" to SentryLevel.DEBUG,
+ "User has made a purchase of product: 445" to SentryLevel.INFO,
+ )
+
+ val logs = server.awaitEnvelope { it.logs() != null }.logs()!!
+ assertThat(logs.items.map { it.body }).contains("User has made a purchase of product: 445")
+ val failureLog = logs.items.single { it.body == "Something went wrong" }
+ assertThat(failureLog.attributes!!.mapValues { it.value.value })
+ .containsAtLeast("user.type", "admin", "feature.version", 2, "debug.enabled", true)
+ }
+}
diff --git a/sentry-log4j2/src/test/resources/sentry-appender-integration-test.xml b/sentry-log4j2/src/test/resources/sentry-appender-integration-test.xml
new file mode 100644
index 00000000000..55ca1301e37
--- /dev/null
+++ b/sentry-log4j2/src/test/resources/sentry-appender-integration-test.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sentry-samples/sentry-samples-log4j2/build.gradle.kts b/sentry-samples/sentry-samples-log4j2/build.gradle.kts
index 901604d2cff..d320d2b3334 100644
--- a/sentry-samples/sentry-samples-log4j2/build.gradle.kts
+++ b/sentry-samples/sentry-samples-log4j2/build.gradle.kts
@@ -1,33 +1,16 @@
-import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
-
plugins {
java
application
- alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.gradle.versions)
alias(libs.plugins.shadow)
- id("io.sentry.systemtest")
}
application { mainClass.set("io.sentry.samples.log4j2.Main") }
-tasks.withType().configureEach {
- kotlin {
- compilerOptions.freeCompilerArgs = listOf("-Xjsr305=strict")
- }
-}
-
dependencies {
implementation(projects.sentryLog4j2)
implementation(libs.log4j.api)
implementation(libs.log4j.core)
-
- testImplementation(kotlin(Config.kotlinStdLib))
- testImplementation(projects.sentry)
- testImplementation(projects.sentrySystemTestSupport)
- testImplementation(libs.kotlin.test.junit)
- testImplementation(libs.slf4j.api)
- testImplementation(libs.slf4j.jdk14)
}
// Configure the Shadow JAR (executable JAR with all dependencies)
@@ -50,22 +33,3 @@ tasks.jar {
// Fix the startScripts task dependency
tasks.startScripts { dependsOn(tasks.shadowJar) }
-
-tasks.register("systemTest").configure {
- group = "verification"
- description = "Runs the System tests"
-
- val test = project.extensions.getByType()["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.*") }
-}
diff --git a/sentry-samples/sentry-samples-log4j2/src/test/kotlin/io/sentry/DummyTest.kt b/sentry-samples/sentry-samples-log4j2/src/test/kotlin/io/sentry/DummyTest.kt
deleted file mode 100644
index 6f762b7e453..00000000000
--- a/sentry-samples/sentry-samples-log4j2/src/test/kotlin/io/sentry/DummyTest.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package io.sentry
-
-import kotlin.test.Test
-import kotlin.test.assertTrue
-
-class DummyTest {
- @Test
- fun `the only test`() {
- // only needed to have more than 0 tests and not fail the build
- assertTrue(true)
- }
-}
diff --git a/sentry-samples/sentry-samples-log4j2/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt b/sentry-samples/sentry-samples-log4j2/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt
deleted file mode 100644
index e22488c2f36..00000000000
--- a/sentry-samples/sentry-samples-log4j2/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt
+++ /dev/null
@@ -1,85 +0,0 @@
-package io.sentry.systemtest
-
-import io.sentry.SentryLevel
-import io.sentry.systemtest.util.TestHelper
-import java.util.concurrent.TimeUnit
-import org.junit.Assert
-import org.junit.Before
-import org.junit.Test
-
-class ConsoleApplicationSystemTest {
- lateinit var testHelper: TestHelper
-
- @Before
- fun setup() {
- testHelper = TestHelper("http://localhost:8000")
- testHelper.reset()
- }
-
- @Test
- fun `log4j2 application sends expected events when run as JAR`() {
- val jarFile = testHelper.findJar("sentry-samples-log4j2")
- val process =
- testHelper.launch(
- jarFile,
- mapOf(
- "SENTRY_DSN" to testHelper.dsn,
- "SENTRY_TRACES_SAMPLE_RATE" to "1.0",
- "SENTRY_ENABLE_PRETTY_SERIALIZATION_OUTPUT" to "false",
- "SENTRY_DEBUG" to "true",
- ),
- )
-
- process.waitFor(30, TimeUnit.SECONDS)
- Assert.assertEquals(0, process.exitValue())
-
- // Verify that we received the expected events
- verifyExpectedEvents()
- }
-
- private fun verifyExpectedEvents() {
- // Verify we received the RuntimeException
- testHelper.ensureErrorReceived { event ->
- event.exceptions?.any { ex ->
- ex.type == "RuntimeException" && ex.value == "Invalid productId=445"
- } == true &&
- event.message?.formatted == "Something went wrong" &&
- event.level?.name == "ERROR"
- }
-
- testHelper.ensureErrorReceived { event ->
- event.message?.message == "Important warning" &&
- testHelper.doesEventHaveFlag(event, "my-feature-flag", true)
- }
-
- testHelper.ensureErrorReceived { event ->
- event.breadcrumbs?.firstOrNull {
- it.message == "Hello Sentry!" && it.level == SentryLevel.DEBUG
- } != null
- }
-
- testHelper.ensureErrorReceived { event ->
- event.breadcrumbs?.firstOrNull {
- it.message == "User has made a purchase of product: 445" && it.level == SentryLevel.INFO
- } != null
- }
-
- testHelper.ensureLogsReceived { logs, _ ->
- testHelper.doesContainLogWithBody(logs, "User has made a purchase of product: 445") &&
- testHelper.doesContainLogWithBody(logs, "Something went wrong") &&
- testHelper.doesLogWithBodyHaveAttribute(
- logs,
- "Something went wrong",
- "user.type",
- "admin",
- ) &&
- testHelper.doesLogWithBodyHaveAttribute(
- logs,
- "Something went wrong",
- "feature.version",
- 2,
- ) &&
- testHelper.doesLogWithBodyHaveAttribute(logs, "Something went wrong", "debug.enabled", true)
- }
- }
-}
diff --git a/test/system-test-runner.py b/test/system-test-runner.py
index 89a95a42007..612d711543e 100644
--- a/test/system-test-runner.py
+++ b/test/system-test-runner.py
@@ -832,7 +832,6 @@ def get_available_modules(self) -> List[ModuleConfig]:
ModuleConfig("sentry-samples-console-otlp", "false", "true", "false"),
ModuleConfig("sentry-samples-console-opentelemetry-noagent", "false", "true", "false"),
ModuleConfig("sentry-samples-logback", "false", "true", "false"),
- ModuleConfig("sentry-samples-log4j2", "false", "true", "false"),
ModuleConfig("sentry-samples-jul", "false", "true", "false"),
]