From a75e37b42643bf641478df07eafa153da6288a55 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 20 Oct 2026 19:33:57 +0200 Subject: [PATCH] test(log4j2): Move the log4j2 sample's system test into sentry-log4j2 Adds SentryAppenderIntegrationTest, which configures the Sentry appender from XML in a logger context of its own and checks what reaches MockSentryServer: the warning event with its context tag and flag, the error with its exception and breadcrumbs, and the logs with their attributes. Logs are enabled through external configuration, as the sample's sentry.properties does, because the appender has no attribute for them. The sample has no system tests left, so it comes off the system-test runner and the backend workflow, and drops the Kotlin plugin it only needed for the test. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/system-tests-backend.yml | 3 - sentry-log4j2/build.gradle.kts | 1 + .../log4j2/SentryAppenderIntegrationTest.kt | 77 +++++++++++++++++ .../sentry-appender-integration-test.xml | 17 ++++ .../sentry-samples-log4j2/build.gradle.kts | 36 -------- .../src/test/kotlin/io/sentry/DummyTest.kt | 12 --- .../ConsoleApplicationSystemTest.kt | 85 ------------------- test/system-test-runner.py | 1 - 8 files changed, 95 insertions(+), 137 deletions(-) create mode 100644 sentry-log4j2/src/test/kotlin/io/sentry/log4j2/SentryAppenderIntegrationTest.kt create mode 100644 sentry-log4j2/src/test/resources/sentry-appender-integration-test.xml delete mode 100644 sentry-samples/sentry-samples-log4j2/src/test/kotlin/io/sentry/DummyTest.kt delete mode 100644 sentry-samples/sentry-samples-log4j2/src/test/kotlin/io/sentry/systemtest/ConsoleApplicationSystemTest.kt 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"), ]