Skip to content
Draft
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
1 change: 1 addition & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ 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-apollo:
maven:io.sentry:sentry-jdbc:
maven:io.sentry:sentry-graphql:
Expand Down
7 changes: 7 additions & 0 deletions .cursor/rules/opentelemetry.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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.

## Advantages over using Sentry without OpenTelemetry

Expand Down Expand Up @@ -86,3 +87,9 @@ 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 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ 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-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
1 change: 1 addition & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ 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_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
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,16 @@
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
}

81 changes: 81 additions & 0 deletions sentry-opentelemetry/sentry-opentelemetry-otlp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
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)

compileOnly(libs.otel)
// 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