Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ targets:
maven:io.sentry:sentry-opentelemetry-agentless-spring:
maven:io.sentry:sentry-opentelemetry-bootstrap:
maven:io.sentry:sentry-opentelemetry-core:
# maven:io.sentry:sentry-opentelemetry-otlp:
# maven:io.sentry:sentry-opentelemetry-otlp-spring:
maven:io.sentry:sentry-apollo:
maven:io.sentry:sentry-jdbc:
maven:io.sentry:sentry-graphql:
Expand Down
9 changes: 9 additions & 0 deletions .cursor/rules/opentelemetry.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ The Sentry Java SDK provides comprehensive OpenTelemetry integration through mul
- `sentry-opentelemetry-agentless-spring`: Spring-specific agentless integration
- `sentry-opentelemetry-bootstrap`: Classes that go into the bootstrap classloader when the agent is used. For agentless they are simply used in the applications classloader.
- `sentry-opentelemetry-agentcustomization`: Classes that help wire up Sentry in OpenTelemetry. These land in the agent classloader when the agent is used. For agentless they are simply used in the application classloader.
- `sentry-opentelemetry-otlp`: Classes for using OpenTelemetry to send spans to Sentry using the OTLP endpoint and have Sentry use OpenTelemetry trace and span id.
- `sentry-opentelemetry-otlp-spring`: Spring Boot convenience module that includes `sentry-opentelemetry-otlp` and the OpenTelemetry Spring Boot starter as transitive dependencies.

## Advantages over using Sentry without OpenTelemetry

Expand Down Expand Up @@ -86,3 +88,10 @@ After creating the transaction with child spans `SentrySpanExporter` uses Sentry
## Troubleshooting

To debug forking of `Scopes`, we added a reference to `parent` `Scopes` and a `creator` String to store the reason why `Scopes` were created or forked.

# OTLP
When using `sentry-opentelemetry-otlp`, Sentry only loads trace ID and span ID from OpenTelemetry `Context` (via `OpenTelemetryOtlpEventProcessor`). Sentry does not rely on OpenTelemetry `Context` for scope storage and propagation, instead relying on its `DefaultScopesStorage`.
It is common to keep Performance in Sentry SDK disabled since that part is taken over by OpenTelemetry.
The `sentry-opentelemetry-otlp` module is not connected to the other `sentry-opentelemetry-*` modules but instead intended only when the goal is to run OpenTelemetry for creating spans and Sentry for other products like errors, logs, metrics etc.
The `sentry-opentelemetry-otlp-spring` module wraps `sentry-opentelemetry-otlp` and includes the OpenTelemetry Spring Boot starter for easier setup in Spring Boot applications.
The OTLP module does not easily work with the OpenTelemetry agent as it would require customizing the agent.JAR in order to get the propagator loaded.
6 changes: 6 additions & 0 deletions .github/workflows/system-tests-backend.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ jobs:
- sample: "sentry-samples-console"
agent: "false"
agent-auto-init: "true"
- sample: "sentry-samples-console-otlp"
agent: "false"
agent-auto-init: "true"
- sample: "sentry-samples-logback"
agent: "false"
agent-auto-init: "true"
Expand All @@ -78,6 +81,9 @@ jobs:
- sample: "sentry-samples-spring-boot-4-opentelemetry"
agent: "true"
agent-auto-init: "false"
- sample: "sentry-samples-spring-boot-4-otlp"
agent: "false"
agent-auto-init: "true"
- sample: "sentry-samples-spring-7"
agent: "false"
agent-auto-init: "true"
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

### Features

- Create `sentry-opentelemetry-otlp` and `sentry-opentelemetry-otlp-spring` modules for combining OpenTelemetry SDK OTLP export with Sentry SDK ([#5100](https://github.com/getsentry/sentry-java/pull/5100))
- OpenTelemetry is configured to send spans to Sentry directly using an OTLP endpoint.
- Sentry only uses trace and span ID from OpenTelemetry (via `OpenTelemetryOtlpEventProcessor`) but will not send spans through OpenTelemetry nor use OpenTelemetry `Context` for `Scopes` propagation.
- See the OTLP setup docs for [Java](https://docs.sentry.io/platforms/java/opentelemetry/setup/otlp/) and [Spring Boot](https://docs.sentry.io/platforms/java/guides/spring-boot/opentelemetry/setup/otlp/) for installation and configuration instructions.
- Add screenshot masking support using view hierarchy ([#5077](https://github.com/getsentry/sentry-java/pull/5077))
- Masks sensitive content (text, images) in error screenshots using the same view hierarchy approach as Session Replay
- Requires the `sentry-android-replay` module to be present at runtime for masking to work
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ Sentry SDK for Java and Android
| sentry-opentelemetry-agent | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-opentelemetry-agent?style=for-the-badge&logo=sentry&color=green) |
| sentry-opentelemetry-agentcustomization | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-opentelemetry-agentcustomization?style=for-the-badge&logo=sentry&color=green) |
| sentry-opentelemetry-core | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-opentelemetry-core?style=for-the-badge&logo=sentry&color=green) |
| sentry-opentelemetry-otlp | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-opentelemetry-otlp?style=for-the-badge&logo=sentry&color=green) |
| sentry-opentelemetry-otlp-spring | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-opentelemetry-otlp-spring?style=for-the-badge&logo=sentry&color=green) |
| sentry-okhttp | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-okhttp?style=for-the-badge&logo=sentry&color=green) |
| sentry-reactor | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-reactor?style=for-the-badge&logo=sentry&color=green) |
| sentry-spotlight | ![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-spotlight?style=for-the-badge&logo=sentry&color=green) |
Expand Down
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ apiValidation {
"sentry-samples-spring-boot-4",
"sentry-samples-spring-boot-4-opentelemetry",
"sentry-samples-spring-boot-4-opentelemetry-noagent",
"sentry-samples-spring-boot-4-otlp",
"sentry-samples-spring-boot-4-webflux",
"sentry-samples-ktor-client",
"sentry-uitest-android",
Expand All @@ -85,7 +86,8 @@ apiValidation {
"test-app-plain",
"test-app-sentry",
"test-app-size",
"sentry-samples-netflix-dgs"
"sentry-samples-netflix-dgs",
"sentry-samples-console-otlp"
)
)
}
Expand Down
2 changes: 2 additions & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ object Config {
val SENTRY_SPRING_BOOT_4_STARTER_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.spring-boot-4-starter"
val SENTRY_OPENTELEMETRY_BOOTSTRAP_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.bootstrap"
val SENTRY_OPENTELEMETRY_CORE_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.core"
val SENTRY_OPENTELEMETRY_OTLP_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.otlp"
val SENTRY_OPENTELEMETRY_OTLP_SPRING_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.otlp-spring"
val SENTRY_OPENTELEMETRY_AGENT_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.agent"
val SENTRY_OPENTELEMETRY_AGENTLESS_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.agentless"
val SENTRY_OPENTELEMETRY_AGENTLESS_SPRING_SDK_NAME = "$SENTRY_JAVA_SDK_NAME.opentelemetry.agentless-spring"
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp" }
openfeature = { module = "dev.openfeature:sdk", version.ref = "openfeature" }
otel = { module = "io.opentelemetry:opentelemetry-sdk", version.ref = "otel" }
otel-exporter-otlp = { module = "io.opentelemetry:opentelemetry-exporter-otlp", version.ref = "otel" }
otel-exporter-logging = { module = "io.opentelemetry:opentelemetry-exporter-logging", version.ref = "otel" }
otel-extension-autoconfigure = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure", version.ref = "otel" }
otel-extension-autoconfigure-spi = { module = "io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi", version.ref = "otel" }
otel-instrumentation-bom = { module = "io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom", version.ref = "otelInstrumentation" }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# sentry-opentelemetry-otlp-spring

This module combines `sentry-opentelemetry-otlp` with the OpenTelemetry Spring Boot Starter for a simpler setup in Spring Boot applications.

It is intended for setups where OpenTelemetry handles tracing (with spans exported via OTLP to Sentry) while Sentry handles errors, logs, metrics, and other products.

Please consult the documentation on how to install and use this integration in the [Sentry Docs for Java](https://docs.sentry.io/platforms/java/opentelemetry/setup/otlp/).
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
plugins {
`java-library`
id("io.sentry.javadoc")
}

dependencies {
api(projects.sentryOpentelemetry.sentryOpentelemetryOtlp)
implementation(libs.springboot3.otel)
}

tasks.jar {
manifest {
attributes(
"Sentry-Version-Name" to project.version,
"Sentry-SDK-Name" to Config.Sentry.SENTRY_OPENTELEMETRY_OTLP_SPRING_SDK_NAME,
"Sentry-SDK-Package-Name" to "maven:io.sentry:sentry-opentelemetry-otlp-spring",
"Implementation-Vendor" to "Sentry",
"Implementation-Title" to project.name,
"Implementation-Version" to project.version,
)
}
}
7 changes: 7 additions & 0 deletions sentry-opentelemetry/sentry-opentelemetry-otlp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# sentry-opentelemetry-otlp

This module provides a lightweight integration for using OpenTelemetry alongside the Sentry SDK. It reads trace and span IDs from the OpenTelemetry `Context` so that Sentry events (errors, logs, metrics) are correlated with OpenTelemetry traces.

Unlike the other `sentry-opentelemetry-*` modules, this module does not rely on OpenTelemetry for scope storage or span creation. It is intended for setups where OpenTelemetry handles performance/tracing and Sentry handles errors, logs, metrics, and other products.

Please consult the documentation on how to install and use this integration in the [Sentry Docs for Java](https://docs.sentry.io/platforms/java/).
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
public final class io/sentry/opentelemetry/otlp/OpenTelemetryOtlpEventProcessor : io/sentry/EventProcessor {
public fun <init> ()V
public fun getOrder ()Ljava/lang/Long;
public fun process (Lio/sentry/SentryEvent;Lio/sentry/Hint;)Lio/sentry/SentryEvent;
public fun process (Lio/sentry/SentryLogEvent;)Lio/sentry/SentryLogEvent;
public fun process (Lio/sentry/SentryMetricsEvent;Lio/sentry/Hint;)Lio/sentry/SentryMetricsEvent;
}

public final class io/sentry/opentelemetry/otlp/OpenTelemetryOtlpPropagator : io/opentelemetry/context/propagation/TextMapPropagator {
public static final field SENTRY_BAGGAGE_KEY Lio/opentelemetry/context/ContextKey;
public fun <init> ()V
public fun extract (Lio/opentelemetry/context/Context;Ljava/lang/Object;Lio/opentelemetry/context/propagation/TextMapGetter;)Lio/opentelemetry/context/Context;
public fun fields ()Ljava/util/Collection;
public fun inject (Lio/opentelemetry/context/Context;Ljava/lang/Object;Lio/opentelemetry/context/propagation/TextMapSetter;)V
}

public final class io/sentry/opentelemetry/otlp/OpenTelemetryOtlpPropagatorProvider : io/opentelemetry/sdk/autoconfigure/spi/ConfigurablePropagatorProvider {
public fun <init> ()V
public fun getName ()Ljava/lang/String;
public fun getPropagator (Lio/opentelemetry/sdk/autoconfigure/spi/ConfigProperties;)Lio/opentelemetry/context/propagation/TextMapPropagator;
}

84 changes: 84 additions & 0 deletions sentry-opentelemetry/sentry-opentelemetry-otlp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import net.ltgt.gradle.errorprone.errorprone
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
`java-library`
id("io.sentry.javadoc")
alias(libs.plugins.kotlin.jvm)
jacoco
alias(libs.plugins.errorprone)
alias(libs.plugins.gradle.versions)
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions.jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_1_8
}

dependencies {
api(projects.sentry)

api(libs.otel)
api(libs.otel.extension.autoconfigure)
api(libs.otel.exporter.otlp)
compileOnly(libs.otel.extension.autoconfigure.spi)
// compileOnly(libs.otel.semconv)
// compileOnly(libs.otel.semconv.incubating)

compileOnly(libs.jetbrains.annotations)
compileOnly(libs.nopen.annotations)
errorprone(libs.errorprone.core)
errorprone(libs.nopen.checker)
errorprone(libs.nullaway)

// tests
testImplementation(projects.sentryTestSupport)
testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(libs.awaitility.kotlin)
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.mockito.kotlin)

testImplementation(libs.otel)
// testImplementation(libs.otel.semconv)
// testImplementation(libs.otel.semconv.incubating)
}

configure<SourceSetContainer> { test { java.srcDir("src/test/java") } }

jacoco { toolVersion = libs.versions.jacoco.get() }

tasks.jacocoTestReport {
reports {
xml.required.set(true)
html.required.set(false)
}
}

tasks {
jacocoTestCoverageVerification {
violationRules { rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } } }
}
check {
dependsOn(jacocoTestCoverageVerification)
dependsOn(jacocoTestReport)
}
}

tasks.withType<JavaCompile>().configureEach {
options.errorprone {
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
option("NullAway:AnnotatedPackages", "io.sentry")
}
}

tasks.jar {
manifest {
attributes(
"Sentry-Version-Name" to project.version,
"Sentry-SDK-Name" to Config.Sentry.SENTRY_OPENTELEMETRY_OTLP_SDK_NAME,
"Sentry-SDK-Package-Name" to "maven:io.sentry:sentry-opentelemetry-otlp",
"Implementation-Vendor" to "Sentry",
"Implementation-Title" to project.name,
"Implementation-Version" to project.version,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package io.sentry.opentelemetry.otlp;

import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanId;
import io.opentelemetry.api.trace.TraceId;
import io.sentry.EventProcessor;
import io.sentry.Hint;
import io.sentry.IScopes;
import io.sentry.ScopesAdapter;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.SentryLogEvent;
import io.sentry.SentryMetricsEvent;
import io.sentry.SpanContext;
import io.sentry.protocol.SentryId;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;

public final class OpenTelemetryOtlpEventProcessor implements EventProcessor {

private final @NotNull IScopes scopes;

public OpenTelemetryOtlpEventProcessor() {
this(ScopesAdapter.getInstance());
}

@TestOnly
OpenTelemetryOtlpEventProcessor(final @NotNull IScopes scopes) {
this.scopes = scopes;
}

@Override
public @Nullable SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) {
@NotNull final Span otelSpan = Span.current();
@NotNull final String traceId = otelSpan.getSpanContext().getTraceId();
@NotNull final String spanId = otelSpan.getSpanContext().getSpanId();

if (TraceId.isValid(traceId) && SpanId.isValid(spanId)) {
final @NotNull SpanContext spanContext =
new SpanContext(
new SentryId(traceId), new io.sentry.SpanId(spanId), "opentelemetry", null, null);

event.getContexts().setTrace(spanContext);
scopes
.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Linking Sentry event %s to span %s created via OpenTelemetry (trace %s).",
event.getEventId(),
spanId,
traceId);
} else {
scopes
.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not linking Sentry event %s to any transaction created via OpenTelemetry as traceId %s or spanId %s are invalid.",
event.getEventId(),
traceId,
spanId);
}

return event;
}

@Override
public @Nullable SentryLogEvent process(@NotNull SentryLogEvent event) {
@NotNull final Span otelSpan = Span.current();
@NotNull final String traceId = otelSpan.getSpanContext().getTraceId();
@NotNull final String spanId = otelSpan.getSpanContext().getSpanId();

if (TraceId.isValid(traceId) && SpanId.isValid(spanId)) {
event.setTraceId(new SentryId(traceId));
event.setSpanId(new io.sentry.SpanId(spanId));
} else {
scopes
.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not linking Sentry event to any transaction created via OpenTelemetry as traceId %s or spanId %s are invalid.",
traceId,
spanId);
}

return event;
}

@Override
public @Nullable SentryMetricsEvent process(
@NotNull SentryMetricsEvent event, @NotNull Hint hint) {
@NotNull final Span otelSpan = Span.current();
@NotNull final String traceId = otelSpan.getSpanContext().getTraceId();
@NotNull final String spanId = otelSpan.getSpanContext().getSpanId();

if (TraceId.isValid(traceId) && SpanId.isValid(spanId)) {
event.setTraceId(new SentryId(traceId));
event.setSpanId(new io.sentry.SpanId(spanId));
} else {
scopes
.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not linking Sentry event to any transaction created via OpenTelemetry as traceId %s or spanId %s are invalid.",
traceId,
spanId);
}

return event;
}

@Override
public @Nullable Long getOrder() {
return 6000L;
}
}
Loading
Loading