From 06a4570fe768aa0113fe9897575553d1d39295ad Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 23 Nov 2022 13:58:38 +0100 Subject: [PATCH 1/4] Allow configuring OTEL Agent via Sentry properties and init in agent --- buildSrc/src/main/java/Config.kt | 1 + .../build.gradle.kts | 4 ++ ...ryAutoConfigurationCustomizerProvider.java | 68 +++++++++++++++++++ .../spring/boot/CustomEventProcessor.java | 35 ---------- .../src/main/resources/application.properties | 19 ------ .../config/ClasspathPropertiesLoader.java | 10 ++- 6 files changed, 80 insertions(+), 57 deletions(-) delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java delete mode 100644 sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index 4141ad9837d..bbacd3150c7 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -200,6 +200,7 @@ object Config { val SENTRY_SPRING_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring" val SENTRY_SPRING_BOOT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot" val SENTRY_SPRING_BOOT_JAKARTA_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot.jakarta" + val SENTRY_OPENTELEMETRY_AGENT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.agent" val group = "io.sentry" val description = "SDK for sentry.io" val versionNameProp = "versionName" diff --git a/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts b/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts index 32f3bf1f84c..495e200f248 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts +++ b/sentry-opentelemetry/sentry-opentelemetry-agent/build.gradle.kts @@ -147,6 +147,10 @@ tasks { attributes.put("Can-Retransform-Classes", "true") attributes.put("Implementation-Vendor", "Sentry") attributes.put("Implementation-Version", "sentry-${project.version}-otel-${Config.Libs.otelJavaagentVersion}") + attributes.put("Sentry-Version-Name", project.version) + attributes.put("Sentry-Opentelemetry-SDK-Name", Config.Sentry.SENTRY_OPENTELEMETRY_AGENT_SDK_NAME) + attributes.put("Sentry-Opentelemetry-Version-Name", Config.Libs.otelVersion) + attributes.put("Sentry-Opentelemetry-Javaagent-Version-Name", Config.Libs.otelJavaagentVersion) } } diff --git a/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java b/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java index fe51dd65e83..6d12c616cc8 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java +++ b/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java @@ -4,19 +4,87 @@ import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; +import io.sentry.Instrumenter; +import io.sentry.Sentry; +import io.sentry.SentryOptions; +import io.sentry.protocol.SdkVersion; +import java.io.IOException; +import java.net.URL; +import java.util.Enumeration; import java.util.HashMap; import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public final class SentryAutoConfigurationCustomizerProvider implements AutoConfigurationCustomizerProvider { @Override public void customize(AutoConfigurationCustomizer autoConfiguration) { + final @Nullable String sentryPropertiesFile = System.getenv("SENTRY_PROPERTIES_FILE"); + final @Nullable String sentryDsn = System.getenv("SENTRY_DSN"); + + if (sentryPropertiesFile != null || sentryDsn != null) { + Sentry.init( + options -> { + options.setEnableExternalConfiguration(sentryPropertiesFile != null); + options.setInstrumenter(Instrumenter.OTEL); + final @Nullable SdkVersion sdkVersion = createSdkVersion(options); + if (sdkVersion != null) { + options.setSdkVersion(sdkVersion); + } + }); + } + autoConfiguration .addTracerProviderCustomizer(this::configureSdkTracerProvider) .addPropertiesSupplier(this::getDefaultProperties); } + private @Nullable SdkVersion createSdkVersion(final @NotNull SentryOptions sentryOptions) { + SdkVersion sdkVersion = sentryOptions.getSdkVersion(); + + try { + final @NotNull Enumeration resources = + ClassLoader.getSystemClassLoader().getResources("META-INF/MANIFEST.MF"); + while (resources.hasMoreElements()) { + try { + final @NotNull Manifest manifest = new Manifest(resources.nextElement().openStream()); + final @Nullable Attributes mainAttributes = manifest.getMainAttributes(); + if (mainAttributes != null) { + final @Nullable String name = mainAttributes.getValue("Sentry-Opentelemetry-SDK-Name"); + final @Nullable String version = mainAttributes.getValue("Sentry-Version-Name"); + + if (name != null && version != null) { + sdkVersion = SdkVersion.updateSdkVersion(sdkVersion, name, version); + sdkVersion.addPackage("maven:io.sentry:sentry-opentelemetry-agent", version); + final @Nullable String otelVersion = + mainAttributes.getValue("Sentry-Opentelemetry-Version-Name"); + if (otelVersion != null) { + sdkVersion.addPackage("maven:io.opentelemetry:opentelemetry-sdk", otelVersion); + } + final @Nullable String otelJavaagentVersion = + mainAttributes.getValue("Sentry-Opentelemetry-Javaagent-Version-Name"); + if (otelJavaagentVersion != null) { + sdkVersion.addPackage( + "maven:io.opentelemetry.javaagent:opentelemetry-javaagent", + otelJavaagentVersion); + } + } + } + } catch (Exception e) { + // ignore + } + } + } catch (IOException e) { + // ignore + } + + return sdkVersion; + } + private SdkTracerProviderBuilder configureSdkTracerProvider( SdkTracerProviderBuilder tracerProvider, ConfigProperties config) { return tracerProvider.addSpanProcessor(new SentrySpanProcessor()); diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java b/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java deleted file mode 100644 index b74310a6761..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/main/java/io/sentry/samples/spring/boot/CustomEventProcessor.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.sentry.samples.spring.boot; - -import io.sentry.EventProcessor; -import io.sentry.Hint; -import io.sentry.SentryEvent; -import io.sentry.protocol.SentryRuntime; -import org.jetbrains.annotations.NotNull; -import org.springframework.boot.SpringBootVersion; -import org.springframework.stereotype.Component; - -/** - * Custom {@link EventProcessor} implementation lets modifying {@link SentryEvent}s before they are - * sent to Sentry. - */ -@Component -public class CustomEventProcessor implements EventProcessor { - private final String springBootVersion; - - public CustomEventProcessor(String springBootVersion) { - this.springBootVersion = springBootVersion; - } - - public CustomEventProcessor() { - this(SpringBootVersion.getVersion()); - } - - @Override - public @NotNull SentryEvent process(@NotNull SentryEvent event, @NotNull Hint hint) { - final SentryRuntime runtime = new SentryRuntime(); - runtime.setVersion(springBootVersion); - runtime.setName("Spring Boot"); - event.getContexts().setRuntime(runtime); - return event; - } -} diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties deleted file mode 100644 index b7f13ee9588..00000000000 --- a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties +++ /dev/null @@ -1,19 +0,0 @@ -# NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry project/dashboard -sentry.dsn=https://502f25099c204a2fbf4cb16edc5975d1@o447951.ingest.sentry.io/5428563 -sentry.send-default-pii=true -sentry.max-request-body-size=medium -# Sentry Spring Boot integration allows more fine-grained SentryOptions configuration -sentry.max-breadcrumbs=150 -# Logback integration configuration options -sentry.logging.minimum-event-level=info -sentry.logging.minimum-breadcrumb-level=debug -# Performance configuration -sentry.traces-sample-rate=1.0 -sentry.debug=true -in-app-includes="io.sentry.samples" - -# Database configuration -spring.datasource.url=jdbc:p6spy:hsqldb:mem:testdb -spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver -spring.datasource.username=sa -spring.datasource.password= diff --git a/sentry/src/main/java/io/sentry/config/ClasspathPropertiesLoader.java b/sentry/src/main/java/io/sentry/config/ClasspathPropertiesLoader.java index 56b2ce83c31..1a730e8f164 100644 --- a/sentry/src/main/java/io/sentry/config/ClasspathPropertiesLoader.java +++ b/sentry/src/main/java/io/sentry/config/ClasspathPropertiesLoader.java @@ -16,14 +16,18 @@ final class ClasspathPropertiesLoader implements PropertiesLoader { private final @NotNull ILogger logger; public ClasspathPropertiesLoader( - @NotNull String fileName, @NotNull ClassLoader classLoader, @NotNull ILogger logger) { + @NotNull String fileName, @Nullable ClassLoader classLoader, @NotNull ILogger logger) { this.fileName = fileName; - this.classLoader = classLoader; + // bootstrap classloader is represented as null, so using system classloader instead + if (classLoader == null) { + this.classLoader = ClassLoader.getSystemClassLoader(); + } else { + this.classLoader = classLoader; + } this.logger = logger; } public ClasspathPropertiesLoader(@NotNull ILogger logger) { - // TODO check not null this("sentry.properties", ClasspathPropertiesLoader.class.getClassLoader(), logger); } From cca09cf930b8ef726efb355d1c6df7af6f7282a6 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Wed, 23 Nov 2022 17:28:46 +0100 Subject: [PATCH 2/4] Always use baggage header if sentr-trace is present on incoming request to make sure baggage is frozen --- .../main/java/io/sentry/opentelemetry/SentryPropagator.java | 6 ++---- sentry/src/main/java/io/sentry/Baggage.java | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentryPropagator.java b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentryPropagator.java index 72e0e489075..85ba4b3b304 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentryPropagator.java +++ b/sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/SentryPropagator.java @@ -75,10 +75,8 @@ public Context extract( Context modifiedContext = context.with(SentryOtelKeys.SENTRY_TRACE_KEY, sentryTraceHeader); final @Nullable String baggageString = getter.get(carrier, BaggageHeader.BAGGAGE_HEADER); - if (baggageString != null) { - Baggage baggage = Baggage.fromHeader(baggageString); - modifiedContext = modifiedContext.with(SentryOtelKeys.SENTRY_BAGGAGE_KEY, baggage); - } + Baggage baggage = Baggage.fromHeader(baggageString); + modifiedContext = modifiedContext.with(SentryOtelKeys.SENTRY_BAGGAGE_KEY, baggage); Span wrappedSpan = Span.wrap(otelSpanContext); modifiedContext = modifiedContext.with(wrappedSpan); diff --git a/sentry/src/main/java/io/sentry/Baggage.java b/sentry/src/main/java/io/sentry/Baggage.java index aca3fdf0fd9..283274d4ded 100644 --- a/sentry/src/main/java/io/sentry/Baggage.java +++ b/sentry/src/main/java/io/sentry/Baggage.java @@ -35,7 +35,7 @@ public final class Baggage { final @NotNull ILogger logger; @NotNull - public static Baggage fromHeader(final String headerValue) { + public static Baggage fromHeader(final @Nullable String headerValue) { return Baggage.fromHeader( headerValue, false, HubAdapter.getInstance().getOptions().getLogger()); } From 687d2fefc97f861bfb4d4212f1c83e64092e8fa6 Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Thu, 24 Nov 2022 15:33:18 +0100 Subject: [PATCH 3/4] Always look for external config for agent init; restore properties file --- ...ryAutoConfigurationCustomizerProvider.java | 2 +- .../src/main/resources/application.properties | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties diff --git a/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java b/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java index 6d12c616cc8..9058ee78550 100644 --- a/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java +++ b/sentry-opentelemetry/sentry-opentelemetry-agentcustomization/src/main/java/io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider.java @@ -29,7 +29,7 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { if (sentryPropertiesFile != null || sentryDsn != null) { Sentry.init( options -> { - options.setEnableExternalConfiguration(sentryPropertiesFile != null); + options.setEnableExternalConfiguration(true); options.setInstrumenter(Instrumenter.OTEL); final @Nullable SdkVersion sdkVersion = createSdkVersion(options); if (sdkVersion != null) { diff --git a/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties new file mode 100644 index 00000000000..b7f13ee9588 --- /dev/null +++ b/sentry-samples/sentry-samples-spring-boot/src/main/resources/application.properties @@ -0,0 +1,19 @@ +# NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in your Sentry project/dashboard +sentry.dsn=https://502f25099c204a2fbf4cb16edc5975d1@o447951.ingest.sentry.io/5428563 +sentry.send-default-pii=true +sentry.max-request-body-size=medium +# Sentry Spring Boot integration allows more fine-grained SentryOptions configuration +sentry.max-breadcrumbs=150 +# Logback integration configuration options +sentry.logging.minimum-event-level=info +sentry.logging.minimum-breadcrumb-level=debug +# Performance configuration +sentry.traces-sample-rate=1.0 +sentry.debug=true +in-app-includes="io.sentry.samples" + +# Database configuration +spring.datasource.url=jdbc:p6spy:hsqldb:mem:testdb +spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver +spring.datasource.username=sa +spring.datasource.password= From 52d86a32dc7c434fc708b735c2e3a587eb51b87d Mon Sep 17 00:00:00 2001 From: Alexander Dinauer Date: Mon, 28 Nov 2022 08:32:37 +0100 Subject: [PATCH 4/4] Add test for finish with end date --- sentry/src/test/java/io/sentry/SentryTracerTest.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sentry/src/test/java/io/sentry/SentryTracerTest.kt b/sentry/src/test/java/io/sentry/SentryTracerTest.kt index 5f0b9f2dd20..fed5bcadc67 100644 --- a/sentry/src/test/java/io/sentry/SentryTracerTest.kt +++ b/sentry/src/test/java/io/sentry/SentryTracerTest.kt @@ -10,6 +10,8 @@ import org.mockito.kotlin.never import org.mockito.kotlin.spy import org.mockito.kotlin.times import org.mockito.kotlin.verify +import java.time.LocalDateTime +import java.time.ZoneOffset import java.util.Date import kotlin.test.Test import kotlin.test.assertEquals @@ -119,6 +121,15 @@ class SentryTracerTest { assertEquals(SpanStatus.ABORTED, tracer.status) } + @Test + fun `when transaction is finished with status and timestamp, timestamp and status are set`() { + val tracer = fixture.getSut() + val date = Date.from(LocalDateTime.of(2022, 12, 24, 23, 59, 58, 0).toInstant(ZoneOffset.UTC)) + tracer.finish(SpanStatus.ABORTED, date) + assertEquals(tracer.timestamp, DateUtils.dateToSeconds(date)) + assertEquals(SpanStatus.ABORTED, tracer.status) + } + @Test fun `when transaction is finished, transaction is captured`() { val tracer = fixture.getSut()