Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POTEL 53 - Automatically set span factory based on presence of OpenTelemetry #3858

Merged
merged 8 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .github/workflows/system-tests-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ jobs:
matrix:
sample: [ "sentry-samples-spring-boot-jakarta" ]
agent: [ "0" ]
agent-auto-init: [ "true" ]
include:
- sample: "sentry-samples-spring-boot"
- sample: "sentry-samples-spring-boot-webflux-jakarta"
- sample: "sentry-samples-spring-boot-webflux"
- sample: "sentry-samples-spring-boot-jakarta-opentelemetry-noagent"
- sample: "sentry-samples-spring-boot-jakarta-opentelemetry"
agent: "1"
agent-auto-init: "true"
- sample: "sentry-samples-spring-boot-jakarta-opentelemetry"
agent: "1"
agent-auto-init: "false"
steps:
- uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -65,13 +71,13 @@ jobs:

- name: Start server and run integration test for sentry-cli commands
run: |
test/system-test-sentry-server-start.sh > sentry-mock-server.txt 2>&1 & test/system-test-spring-server-start.sh "${{ matrix.sample }}" "${{ matrix.agent }}" > spring-server.txt 2>&1 & test/wait-for-spring.sh && ./gradlew :sentry-samples:${{ matrix.sample }}:systemTest
test/system-test-run.sh "${{ matrix.sample }}" "${{ matrix.agent }}" "${{ matrix.agent-auto-init }}"

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.sample }}-system-test
name: test-results-${{ matrix.sample }}-${{ matrix.agent }}-${{ matrix.agent-auto-init }}-system-test
path: |
**/build/reports/*
sentry-mock-server.txt
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ apiValidation {
"sentry-samples-spring-boot",
"sentry-samples-spring-boot-jakarta",
"sentry-samples-spring-boot-jakarta-opentelemetry",
"sentry-samples-spring-boot-jakarta-opentelemetry-noagent",
"sentry-samples-spring-boot-webflux",
"sentry-samples-spring-boot-webflux-jakarta",
"sentry-uitest-android",
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ object Config {
val springBoot3StarterSecurity = "org.springframework.boot:spring-boot-starter-security:$springBoot3Version"
val springBoot3StarterJdbc = "org.springframework.boot:spring-boot-starter-jdbc:$springBoot3Version"
val springBoot3StarterActuator = "org.springframework.boot:spring-boot-starter-actuator:$springBoot3Version"
val springBoot3StarterOpenTelemetry = "io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter:${OpenTelemetry.otelJavaagentVersion}"

val springWeb = "org.springframework:spring-webmvc"
val springWebflux = "org.springframework:spring-webflux"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,8 @@ class SentryAndroidTest {
}
}

@Test
@Config(sdk = [30])
// @Test
// @Config(sdk = [30])
fun `AnrV2 events get enriched with previously persisted scope and options data, the new data gets persisted after that`() {
val cacheDir = tmpDir.newFolder().absolutePath
fixture.addAppExitInfo(timestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1))
Expand Down
16 changes: 14 additions & 2 deletions sentry-opentelemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,21 @@ application. Please see the module [README](sentry-opentelemetry-agent/README.md
This contains customizations to the OpenTelemetry Java Agent such as registering the
`SentrySpanProcessor` and `SentryPropagator` as well as providing default properties that
enable the `sentry` propagator and disable exporters so our agent doesn't trigger lots of log
warnings due to OTLP server not being there.
warnings due to OTLP server not being there. This can also be used without the agent.

### `sentry-opentelemetry-bootstrap`

Classes that are loaded into the bootstrap classloader
(represented as `null` when invoking X.class.classLoader)
These are shared between the agent and the application and include things like storage,
utils, factory, tokens etc.

If you want to use Sentry with OpenTelemetry without the agent,
you also need this module as a dependency.

### `sentry-opentelemetry-core`

Contains `SentrySpanProcessor` and `SentryPropagator` which are used by our Java Agent but can also
be used when manually instrumenting using OpenTelemetry.
be used when manually instrumenting using OpenTelemetry. If you want to use OpenTelemetry without
the agent but still want some configuration convenience, you should rather use the
`sentry-opentelemetry-agentcustomization` module.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ val upstreamAgent = configurations.create("upstreamAgent") {
dependencies {
bootstrapLibs(projects.sentry)
bootstrapLibs(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap)
bootstrapLibs(projects.sentryOpentelemetry.sentryOpentelemetryExtra)
javaagentLibs(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization)
upstreamAgent(Config.Libs.OpenTelemetry.otelJavaAgent)
}
Expand Down Expand Up @@ -164,3 +163,7 @@ tasks {
dependsOn(shadowJar)
}
}

tasks.named("distZip").configure {
this.dependsOn("jar")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.sentry.opentelemetry.agent;

/**
* Marker class used to check if the Sentry Java Agent is active. If so, this class should be on the
* classpath.
*/
public final class AgentMarker {}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
public final class io/sentry/opentelemetry/SentryAutoConfigurationCustomizerProvider : io/opentelemetry/sdk/autoconfigure/spi/AutoConfigurationCustomizerProvider {
public static field skipInit Z
public fun <init> ()V
public fun customize (Lio/opentelemetry/sdk/autoconfigure/spi/AutoConfigurationCustomizer;)V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ dependencies {
exclude(group = "io.opentelemetry.javaagent")
}
compileOnly(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap)
implementation(projects.sentryOpentelemetry.sentryOpentelemetryExtra)

compileOnly(Config.Libs.OpenTelemetry.otelSdk)
compileOnly(Config.Libs.OpenTelemetry.otelExtensionAutoconfigureSpi)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.sentry.opentelemetry;

import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
Expand All @@ -10,10 +9,8 @@
import io.sentry.Sentry;
import io.sentry.SentryIntegrationPackageStorage;
import io.sentry.SentryOptions;
import io.sentry.SentrySpanFactoryHolder;
import io.sentry.protocol.SdkVersion;
import io.sentry.protocol.SentryPackage;
import io.sentry.util.SpanUtils;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
Expand All @@ -29,31 +26,19 @@
public final class SentryAutoConfigurationCustomizerProvider
implements AutoConfigurationCustomizerProvider {

public static volatile boolean skipInit = false;

@Override
public void customize(AutoConfigurationCustomizer autoConfiguration) {
ensureSentryOtelStorageIsInitialized();
final @Nullable VersionInfoHolder versionInfoHolder = createVersionInfo();

final @NotNull OtelSpanFactory spanFactory = new OtelSpanFactory();
SentrySpanFactoryHolder.setSpanFactory(spanFactory);
/**
* We're currently overriding the storage mechanism to allow for cleanup of non closed OTel
* scopes. These happen when using e.g. Sentry static API due to getCurrentScopes() invoking
* Context.makeCurrent and then ignoring the returned lifecycle token (OTel Scope). After fixing
* the classloader problem (sentry bootstrap dependency is currently in agent classloader) we
* can revisit and try again to set the storage instead of overriding it in the wrapper. We
* should try to use OTels StorageProvider mechanism instead.
*/
// ContextStorage.addWrapper((storage) -> new SentryContextStorage(storage));
ContextStorage.addWrapper(
(storage) -> new SentryContextStorage(new SentryOtelThreadLocalStorage()));

if (isSentryAutoInitEnabled()) {
Sentry.init(
options -> {
options.setEnableExternalConfiguration(true);
options.setInitPriority(InitPriority.HIGH);
options.setIgnoredSpanOrigins(SpanUtils.ignoredSpanOriginsForOpenTelemetry());
options.setSpanFactory(spanFactory);
OpenTelemetryUtil.applyOpenTelemetryOptions(options, true);
final @Nullable SdkVersion sdkVersion = createSdkVersion(options, versionInfoHolder);
if (sdkVersion != null) {
options.setSdkVersion(sdkVersion);
Expand All @@ -75,7 +60,19 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
.addPropertiesSupplier(this::getDefaultProperties);
}

private static void ensureSentryOtelStorageIsInitialized() {
/*
accessing Sentry.something will cause ScopesStorageFactory to run,
which causes OtelContextScopesStorage.init to register SentryContextStorage
as a wrapper. The wrapper can only be set until storage has been initialized by OpenTelemetry.
*/
Sentry.getGlobalScope();
}

private boolean isSentryAutoInitEnabled() {
if (skipInit) {
return false;
}
final @Nullable String sentryAutoInit = System.getenv("SENTRY_AUTO_INIT");

if (sentryAutoInit != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
public abstract interface class io/sentry/opentelemetry/IOtelSpanWrapper : io/sentry/ISpan {
public abstract fun getData ()Ljava/util/Map;
public abstract fun getMeasurements ()Ljava/util/Map;
public abstract fun getScopes ()Lio/sentry/IScopes;
public abstract fun getTags ()Ljava/util/Map;
public abstract fun getTraceId ()Lio/sentry/protocol/SentryId;
public abstract fun getTransactionName ()Ljava/lang/String;
public abstract fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
public abstract fun isProfileSampled ()Ljava/lang/Boolean;
public abstract fun setTransactionName (Ljava/lang/String;)V
public abstract fun setTransactionName (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
}

public final class io/sentry/opentelemetry/InternalSemanticAttributes {
public static final field BAGGAGE Lio/opentelemetry/api/common/AttributeKey;
public static final field BAGGAGE_MUTABLE Lio/opentelemetry/api/common/AttributeKey;
Expand All @@ -10,10 +23,108 @@ public final class io/sentry/opentelemetry/InternalSemanticAttributes {
public fun <init> ()V
}

public final class io/sentry/opentelemetry/OpenTelemetryUtil {
public fun <init> ()V
public static fun applyOpenTelemetryOptions (Lio/sentry/SentryOptions;Z)V
}

public final class io/sentry/opentelemetry/OtelContextScopesStorage : io/sentry/IScopesStorage {
public fun <init> ()V
public fun close ()V
public fun get ()Lio/sentry/IScopes;
public fun init ()V
public fun set (Lio/sentry/IScopes;)Lio/sentry/ISentryLifecycleToken;
}

public final class io/sentry/opentelemetry/OtelSpanFactory : io/sentry/ISpanFactory {
public fun <init> ()V
public fun <init> (Lio/opentelemetry/api/OpenTelemetry;)V
public fun createSpan (Lio/sentry/IScopes;Lio/sentry/SpanOptions;Lio/sentry/SpanContext;Lio/sentry/ISpan;)Lio/sentry/ISpan;
public fun createTransaction (Lio/sentry/TransactionContext;Lio/sentry/IScopes;Lio/sentry/TransactionOptions;Lio/sentry/TransactionPerformanceCollector;)Lio/sentry/ITransaction;
}

public final class io/sentry/opentelemetry/OtelTransactionSpanForwarder : io/sentry/ITransaction {
public fun <init> (Lio/sentry/opentelemetry/IOtelSpanWrapper;)V
public fun finish ()V
public fun finish (Lio/sentry/SpanStatus;)V
public fun finish (Lio/sentry/SpanStatus;Lio/sentry/SentryDate;)V
public fun finish (Lio/sentry/SpanStatus;Lio/sentry/SentryDate;ZLio/sentry/Hint;)V
public fun forceFinish (Lio/sentry/SpanStatus;ZLio/sentry/Hint;)V
public fun getContexts ()Lio/sentry/protocol/Contexts;
public fun getData (Ljava/lang/String;)Ljava/lang/Object;
public fun getDescription ()Ljava/lang/String;
public fun getEventId ()Lio/sentry/protocol/SentryId;
public fun getFinishDate ()Lio/sentry/SentryDate;
public fun getLatestActiveSpan ()Lio/sentry/ISpan;
public fun getName ()Ljava/lang/String;
public fun getOperation ()Ljava/lang/String;
public fun getSamplingDecision ()Lio/sentry/TracesSamplingDecision;
public fun getSpanContext ()Lio/sentry/SpanContext;
public fun getSpans ()Ljava/util/List;
public fun getStartDate ()Lio/sentry/SentryDate;
public fun getStatus ()Lio/sentry/SpanStatus;
public fun getTag (Ljava/lang/String;)Ljava/lang/String;
public fun getThrowable ()Ljava/lang/Throwable;
public fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
public fun isFinished ()Z
public fun isNoOp ()Z
public fun isProfileSampled ()Ljava/lang/Boolean;
public fun isSampled ()Ljava/lang/Boolean;
public fun makeCurrent ()Lio/sentry/ISentryLifecycleToken;
public fun scheduleFinish ()V
public fun setContext (Ljava/lang/String;Ljava/lang/Object;)V
public fun setData (Ljava/lang/String;Ljava/lang/Object;)V
public fun setDescription (Ljava/lang/String;)V
public fun setMeasurement (Ljava/lang/String;Ljava/lang/Number;)V
public fun setMeasurement (Ljava/lang/String;Ljava/lang/Number;Lio/sentry/MeasurementUnit;)V
public fun setName (Ljava/lang/String;)V
public fun setName (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
public fun setOperation (Ljava/lang/String;)V
public fun setStatus (Lio/sentry/SpanStatus;)V
public fun setTag (Ljava/lang/String;Ljava/lang/String;)V
public fun setThrowable (Ljava/lang/Throwable;)V
public fun startChild (Lio/sentry/SpanContext;Lio/sentry/SpanOptions;)Lio/sentry/ISpan;
public fun startChild (Ljava/lang/String;)Lio/sentry/ISpan;
public fun startChild (Ljava/lang/String;Ljava/lang/String;)Lio/sentry/ISpan;
public fun startChild (Ljava/lang/String;Ljava/lang/String;Lio/sentry/SentryDate;)Lio/sentry/ISpan;
public fun startChild (Ljava/lang/String;Ljava/lang/String;Lio/sentry/SentryDate;Lio/sentry/Instrumenter;)Lio/sentry/ISpan;
public fun startChild (Ljava/lang/String;Ljava/lang/String;Lio/sentry/SentryDate;Lio/sentry/Instrumenter;Lio/sentry/SpanOptions;)Lio/sentry/ISpan;
public fun startChild (Ljava/lang/String;Ljava/lang/String;Lio/sentry/SpanOptions;)Lio/sentry/ISpan;
public fun toBaggageHeader (Ljava/util/List;)Lio/sentry/BaggageHeader;
public fun toSentryTrace ()Lio/sentry/SentryTraceHeader;
public fun traceContext ()Lio/sentry/TraceContext;
public fun updateEndDate (Lio/sentry/SentryDate;)Z
}

public final class io/sentry/opentelemetry/SentryContextStorage : io/opentelemetry/context/ContextStorage {
public fun <init> (Lio/opentelemetry/context/ContextStorage;)V
public fun attach (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Scope;
public fun current ()Lio/opentelemetry/context/Context;
}

public final class io/sentry/opentelemetry/SentryContextWrapper : io/opentelemetry/context/Context {
public fun get (Lio/opentelemetry/context/ContextKey;)Ljava/lang/Object;
public fun toString ()Ljava/lang/String;
public fun with (Lio/opentelemetry/context/ContextKey;Ljava/lang/Object;)Lio/opentelemetry/context/Context;
public static fun wrap (Lio/opentelemetry/context/Context;)Lio/sentry/opentelemetry/SentryContextWrapper;
}

public final class io/sentry/opentelemetry/SentryOtelKeys {
public static final field SENTRY_BAGGAGE_KEY Lio/opentelemetry/context/ContextKey;
public static final field SENTRY_SCOPES_KEY Lio/opentelemetry/context/ContextKey;
public static final field SENTRY_TRACE_KEY Lio/opentelemetry/context/ContextKey;
public fun <init> ()V
}

public final class io/sentry/opentelemetry/SentryOtelThreadLocalStorage : io/opentelemetry/context/ContextStorage {
public fun <init> ()V
public fun attach (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Scope;
public fun current ()Lio/opentelemetry/context/Context;
}

public final class io/sentry/opentelemetry/SentryWeakSpanStorage {
public static fun getInstance ()Lio/sentry/opentelemetry/SentryWeakSpanStorage;
public fun getSentrySpan (Lio/opentelemetry/api/trace/SpanContext;)Lio/sentry/opentelemetry/IOtelSpanWrapper;
public fun storeSentrySpan (Lio/opentelemetry/api/trace/SpanContext;Lio/sentry/opentelemetry/IOtelSpanWrapper;)V
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.sentry.opentelemetry;

import io.sentry.IScopes;
import io.sentry.ISpan;
import io.sentry.protocol.MeasurementValue;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.TransactionNameSource;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public interface IOtelSpanWrapper extends ISpan {

void setTransactionName(@NotNull String name);

void setTransactionName(@NotNull String name, @NotNull TransactionNameSource nameSource);

@ApiStatus.Internal
@Nullable
TransactionNameSource getTransactionNameSource();

@ApiStatus.Internal
@Nullable
String getTransactionName();

@NotNull
SentryId getTraceId();

@NotNull
Map<String, Object> getData();

@NotNull
Map<String, MeasurementValue> getMeasurements();

@Nullable
Boolean isProfileSampled();

@ApiStatus.Internal
@NotNull
IScopes getScopes();

@ApiStatus.Internal
@NotNull
Map<String, String> getTags();
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package io.sentry.opentelemetry;

import io.sentry.SentryOptions;
import io.sentry.SentrySpanFactoryHolder;
import io.sentry.util.SpanUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

@ApiStatus.Experimental
public final class OpenTelemetryUtil {

public static void applyOpenTelemetryOptions(final @Nullable SentryOptions options) {
public static void applyOpenTelemetryOptions(
final @Nullable SentryOptions options, final boolean isAgent) {
if (options != null) {
options.setSpanFactory(SentrySpanFactoryHolder.getSpanFactory());
options.setIgnoredSpanOrigins(SpanUtils.ignoredSpanOriginsForOpenTelemetry());
options.setIgnoredSpanOrigins(SpanUtils.ignoredSpanOriginsForOpenTelemetry(isAgent));
}
}
}
Loading
Loading