From 7ed96693530b8fe872a9a9315417ef8145f0bdd9 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Fri, 29 Nov 2019 11:22:25 +0100 Subject: [PATCH] Logging Sentry Fixes #3985 --- bom/deployment/pom.xml | 5 ++ bom/runtime/pom.xml | 12 +++- extensions/logging-sentry/deployment/pom.xml | 65 +++++++++++++++++++ .../sentry/deployment/SentryProcessor.java | 33 ++++++++++ .../sentry/SentryLoggerCustomTest.java | 50 ++++++++++++++ .../logging/sentry/SentryLoggerTest.java | 50 ++++++++++++++ .../java/io/sentry/jvmti/ResetFrameCache.java | 8 +++ ...pplication-sentry-logger-custom.properties | 4 ++ ...plication-sentry-logger-default.properties | 2 + extensions/logging-sentry/pom.xml | 21 ++++++ extensions/logging-sentry/runtime/pom.xml | 48 ++++++++++++++ .../quarkus/logging/sentry/SentryConfig.java | 50 ++++++++++++++ .../logging/sentry/SentryConfigProvider.java | 29 +++++++++ .../sentry/SentryHandlerValueFactory.java | 33 ++++++++++ .../resources/META-INF/quarkus-extension.yaml | 10 +++ extensions/pom.xml | 3 +- 16 files changed, 421 insertions(+), 2 deletions(-) create mode 100644 extensions/logging-sentry/deployment/pom.xml create mode 100644 extensions/logging-sentry/deployment/src/main/java/io/quarkus/logging/sentry/deployment/SentryProcessor.java create mode 100644 extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java create mode 100644 extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java create mode 100644 extensions/logging-sentry/deployment/src/test/java/io/sentry/jvmti/ResetFrameCache.java create mode 100644 extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-custom.properties create mode 100644 extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties create mode 100644 extensions/logging-sentry/pom.xml create mode 100644 extensions/logging-sentry/runtime/pom.xml create mode 100644 extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java create mode 100644 extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java create mode 100644 extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java create mode 100644 extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index b2bfe1fc085c78..afca6dec90c28d 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -501,6 +501,11 @@ quarkus-logging-json-deployment ${project.version} + + io.quarkus + quarkus-logging-sentry-deployment + ${project.version} + io.quarkus diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 41e1555e98e13e..3ba0a6b3add6b8 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -153,6 +153,7 @@ 1.11.0 2.10.1 3.12.6 + 1.7.28 3.1.0 3.1.7 @@ -710,6 +711,11 @@ quarkus-logging-json ${project.version} + + io.quarkus + quarkus-logging-sentry + ${project.version} + @@ -1345,7 +1351,11 @@ okhttp ${okhttp.version} - + + io.sentry + sentry + ${sentry.version} + org.apache.maven.plugin-tools maven-plugin-annotations diff --git a/extensions/logging-sentry/deployment/pom.xml b/extensions/logging-sentry/deployment/pom.xml new file mode 100644 index 00000000000000..df0b5a712d4e07 --- /dev/null +++ b/extensions/logging-sentry/deployment/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + io.quarkus + quarkus-logging-sentry-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-logging-sentry-deployment + Quarkus - Logging - Sentry - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-logging-sentry + + + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-junit5 + test + + + org.assertj + assertj-core + test + + + io.quarkus + quarkus-arc-deployment + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + diff --git a/extensions/logging-sentry/deployment/src/main/java/io/quarkus/logging/sentry/deployment/SentryProcessor.java b/extensions/logging-sentry/deployment/src/main/java/io/quarkus/logging/sentry/deployment/SentryProcessor.java new file mode 100644 index 00000000000000..f301eb67b2a861 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/main/java/io/quarkus/logging/sentry/deployment/SentryProcessor.java @@ -0,0 +1,33 @@ +package io.quarkus.logging.sentry.deployment; + +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.LogHandlerBuildItem; +import io.quarkus.logging.sentry.SentryConfig; +import io.quarkus.logging.sentry.SentryHandlerValueFactory; + +class SentryProcessor { + + private static final String FEATURE = "sentry"; + + @BuildStep + FeatureBuildItem feature() { + return new FeatureBuildItem(FEATURE); + } + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + LogHandlerBuildItem addSentryLogHandler(final SentryConfig sentryConfig, + final SentryHandlerValueFactory sentryHandlerValueFactory) { + return new LogHandlerBuildItem(sentryHandlerValueFactory.create(sentryConfig)); + } + + @BuildStep + ExtensionSslNativeSupportBuildItem activateSslNativeSupport() { + return new ExtensionSslNativeSupportBuildItem(FEATURE); + } + +} diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java new file mode 100644 index 00000000000000..43a0d912caef32 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerCustomTest.java @@ -0,0 +1,50 @@ +package io.quarkus.logging.sentry; + +import static io.sentry.jvmti.ResetFrameCache.resetFrameCache; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.jboss.logmanager.handlers.DelayedHandler; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.logging.InitialConfigurator; +import io.quarkus.test.QuarkusUnitTest; +import io.sentry.jul.SentryHandler; +import io.sentry.jvmti.FrameCache; + +public class SentryLoggerCustomTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-sentry-logger-custom.properties"); + + @Test + public void sentryLoggerCustomTest() { + LogManager logManager = LogManager.getLogManager(); + assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); + + DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; + assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); + assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); + + Handler handler = Arrays.stream(delayedHandler.getHandlers()) + .filter(h -> (h instanceof SentryHandler)) + .findFirst().orElse(null); + SentryHandler sentryHandler = (SentryHandler) handler; + assertThat(sentryHandler).isNotNull(); + assertThat(sentryHandler.getLevel()).isEqualTo(org.jboss.logmanager.Level.TRACE); + assertThat(FrameCache.shouldCacheThrowable(new IllegalStateException("Test frame"), 1)).isTrue(); + } + + @AfterAll + static void reset() { + resetFrameCache(); + } +} diff --git a/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java new file mode 100644 index 00000000000000..805a9bb7c83a19 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/java/io/quarkus/logging/sentry/SentryLoggerTest.java @@ -0,0 +1,50 @@ +package io.quarkus.logging.sentry; + +import static io.sentry.jvmti.ResetFrameCache.resetFrameCache; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.Logger; + +import org.jboss.logmanager.handlers.DelayedHandler; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.runtime.logging.InitialConfigurator; +import io.quarkus.test.QuarkusUnitTest; +import io.sentry.jul.SentryHandler; +import io.sentry.jvmti.FrameCache; + +public class SentryLoggerTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("application-sentry-logger-default.properties"); + + @Test + public void sentryLoggerDefaultTest() { + LogManager logManager = LogManager.getLogManager(); + assertThat(logManager).isInstanceOf(org.jboss.logmanager.LogManager.class); + + DelayedHandler delayedHandler = InitialConfigurator.DELAYED_HANDLER; + assertThat(Logger.getLogger("").getHandlers()).contains(delayedHandler); + assertThat(delayedHandler.getLevel()).isEqualTo(Level.ALL); + + Handler handler = Arrays.stream(delayedHandler.getHandlers()) + .filter(h -> (h instanceof SentryHandler)) + .findFirst().orElse(null); + SentryHandler sentryHandler = (SentryHandler) handler; + assertThat(sentryHandler).isNotNull(); + assertThat(sentryHandler.getLevel()).isEqualTo(org.jboss.logmanager.Level.WARN); + assertThat(FrameCache.shouldCacheThrowable(new IllegalStateException("Test frame"), 1)).isFalse(); + } + + @AfterAll + static void reset() { + resetFrameCache(); + } +} diff --git a/extensions/logging-sentry/deployment/src/test/java/io/sentry/jvmti/ResetFrameCache.java b/extensions/logging-sentry/deployment/src/test/java/io/sentry/jvmti/ResetFrameCache.java new file mode 100644 index 00000000000000..5b8156ddf6f33c --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/java/io/sentry/jvmti/ResetFrameCache.java @@ -0,0 +1,8 @@ +package io.sentry.jvmti; + +public class ResetFrameCache { + + public static void resetFrameCache() { + FrameCache.reset(); + } +} diff --git a/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-custom.properties b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-custom.properties new file mode 100644 index 00000000000000..ebe3085388d2b0 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-custom.properties @@ -0,0 +1,4 @@ +quarkus.log.sentry=true +quarkus.log.sentry.dsn=https://123@test.io/22222 +quarkus.log.sentry.level=TRACE +quarkus.log.sentry.in-app-packages=io.quarkus.logging.sentry,org.test \ No newline at end of file diff --git a/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties new file mode 100644 index 00000000000000..1ead8dc2a62943 --- /dev/null +++ b/extensions/logging-sentry/deployment/src/test/resources/application-sentry-logger-default.properties @@ -0,0 +1,2 @@ +quarkus.log.sentry=true +quarkus.log.sentry.dsn=https://123@test.io/22222 \ No newline at end of file diff --git a/extensions/logging-sentry/pom.xml b/extensions/logging-sentry/pom.xml new file mode 100644 index 00000000000000..c73a89369c4840 --- /dev/null +++ b/extensions/logging-sentry/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../build-parent/pom.xml + + quarkus-logging-sentry-parent + Quarkus - Logging - Sentry + + pom + + + deployment + runtime + + diff --git a/extensions/logging-sentry/runtime/pom.xml b/extensions/logging-sentry/runtime/pom.xml new file mode 100644 index 00000000000000..81072784e14241 --- /dev/null +++ b/extensions/logging-sentry/runtime/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + io.quarkus + quarkus-logging-sentry-parent + 999-SNAPSHOT + ../pom.xml + + + quarkus-logging-sentry + Quarkus - Logging - Sentry - Runtime + Self-hosted and cloud-based error monitoring that helps software teams discover, triage, and prioritize errors in real-time. + + + + io.quarkus + quarkus-core + + + io.sentry + sentry + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java new file mode 100644 index 00000000000000..fb70a20f788199 --- /dev/null +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfig.java @@ -0,0 +1,50 @@ +package io.quarkus.logging.sentry; + +import java.util.List; +import java.util.Optional; +import java.util.logging.Level; + +import io.quarkus.runtime.annotations.ConfigItem; +import io.quarkus.runtime.annotations.ConfigPhase; +import io.quarkus.runtime.annotations.ConfigRoot; + +/** + * Configuration for Sentry logging. + */ +@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "log.sentry") +public class SentryConfig { + + /** + * Determine whether to enable the Sentry logging extension. + */ + @ConfigItem(name = ConfigItem.PARENT) + boolean enable; + + /** + * Sentry DSN + * + * The DSN is the first and most important thing to configure because it tells the SDK where to send events. You can find + * your project’s DSN in the “Client Keys” section of your “Project Settings” in Sentry. + */ + @ConfigItem + public String dsn; + + /** + * The sentry log level. + */ + @ConfigItem(defaultValue = "WARN") + public Level level; + + /** + * Sentry differentiates stack frames that are directly related to your application (“in application”) from stack frames + * that come from other packages such as the standard library, frameworks, or other dependencies. The difference is visible + * in the Sentry web interface where only the “in application” frames are displayed by default. + * + * You can configure which package prefixes your application uses with this option. + * + * This option is highly recommended as it affects stacktrace grouping and display on Sentry. See documentation: + * https://docs.sentry.io/clients/java/config/#in-application-stack-frames + */ + @ConfigItem + public Optional> inAppPackages; +} diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java new file mode 100644 index 00000000000000..17b854eaafa457 --- /dev/null +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryConfigProvider.java @@ -0,0 +1,29 @@ +package io.quarkus.logging.sentry; + +import static java.lang.String.join; + +import io.sentry.DefaultSentryClientFactory; +import io.sentry.config.provider.ConfigurationProvider; + +/** + * Mapping between the SentryConfig and the Sentry options {@link io.sentry.DefaultSentryClientFactory} + */ +class SentryConfigProvider implements ConfigurationProvider { + + private final SentryConfig config; + + SentryConfigProvider(SentryConfig config) { + this.config = config; + } + + @Override + public String getProperty(String key) { + switch (key) { + case DefaultSentryClientFactory.IN_APP_FRAMES_OPTION: + return config.inAppPackages.map(p -> join(",", p)).orElse(""); + // New SentryConfig options should be mapped here + default: + return null; + } + } +} diff --git a/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java new file mode 100644 index 00000000000000..f415ae7820f623 --- /dev/null +++ b/extensions/logging-sentry/runtime/src/main/java/io/quarkus/logging/sentry/SentryHandlerValueFactory.java @@ -0,0 +1,33 @@ +package io.quarkus.logging.sentry; + +import java.util.Optional; +import java.util.logging.Handler; + +import org.jboss.logging.Logger; + +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; +import io.sentry.Sentry; +import io.sentry.SentryOptions; +import io.sentry.config.Lookup; +import io.sentry.jul.SentryHandler; + +@Recorder +public class SentryHandlerValueFactory { + private static final Logger LOG = Logger.getLogger(SentryConfigProvider.class); + + public RuntimeValue> create(final SentryConfig config) { + if (!config.enable) { + return new RuntimeValue<>(Optional.empty()); + } + if (!config.inAppPackages.isPresent()) { + LOG.warn( + "No 'quarkus.sentry.in-app-packages' was configured, this option is highly recommended as it affects stacktrace grouping and display on Sentry."); + } + final SentryConfigProvider provider = new SentryConfigProvider(config); + Sentry.init(SentryOptions.from(new Lookup(provider, provider), config.dsn)); + SentryHandler handler = new SentryHandler(); + handler.setLevel(config.level); + return new RuntimeValue<>(Optional.of(handler)); + } +} diff --git a/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 00000000000000..a92de8ea4fc95d --- /dev/null +++ b/extensions/logging-sentry/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,10 @@ +--- +name: "Logging Sentry" +metadata: + keywords: + - "logging" + - "sentry" + - "cloud" + categories: + - "core" + status: "preview" \ No newline at end of file diff --git a/extensions/pom.xml b/extensions/pom.xml index 29ed60c98436c4..6370143a8565fa 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -133,7 +133,8 @@ logging-json - + logging-sentry + qute