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"), ]