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

Auto add OpenTelemetryLinkErrorEventProcessor for Spring Boot #2429

Merged
merged 11 commits into from Dec 22, 2022
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,11 @@

- Use minSdk compatible `Objects` class ([#2436](https://github.com/getsentry/sentry-java/pull/2436))

### Features

- Add logging for OpenTelemetry integration ([#2425](https://github.com/getsentry/sentry-java/pull/2425))
- Auto add `OpenTelemetryLinkErrorEventProcessor` for Spring Boot ([#2429](https://github.com/getsentry/sentry-java/pull/2429))

## 6.10.0

### Features
Expand Down
Expand Up @@ -20,7 +20,10 @@ tasks.withType<KotlinCompile>().configureEach {

dependencies {
compileOnly(projects.sentry)
implementation(projects.sentryOpentelemetry.sentryOpentelemetryCore)
implementation(projects.sentryOpentelemetry.sentryOpentelemetryCore) {
exclude(group = "io.opentelemetry")
exclude(group = "io.opentelemetry.javaagent")
}

compileOnly(Config.Libs.OpenTelemetry.otelSdk)
compileOnly(Config.Libs.OpenTelemetry.otelExtensionAutoconfigureSpi)
Expand Down
Expand Up @@ -21,7 +21,7 @@ tasks.withType<KotlinCompile>().configureEach {
dependencies {
compileOnly(projects.sentry)

compileOnly(Config.Libs.OpenTelemetry.otelSdk)
implementation(Config.Libs.OpenTelemetry.otelSdk)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, why did we change it to implementation? It's not possible to use this module without otel, right? Or is the assumption that people will just include our sentry-opentelemetry-core package, but not otel itself?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intention was to make it easier to use sentry-opentelemetry-agent with SENTRY_AUTO_INIT=false. Currently you need to add sentry-opentelemetry-core to the dependencies so you can enjoy linking of errors to transactions. With compileOnly you'd also have to include the correct version of the OTEL SDK, with implementation it just drags it in. For sentry-opentelemetry-agent we exclude it again, which users also have to do, if they don't want to use the agent but have a different OTEL version than we use or their build system can't figure out versions.

Another approach (for later) would be to have yet another gradle module, that we can then add to the bootstrap classloader in the agent, thus not requiring the explicit dependency on sentry-opentelemetry-core at all, when setting SENTRY_AUTO_INIT=false.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright, just beware that implementation will override whatever version the users are using (if it's lower than ours). If that's what we want, then I'm good 👍

compileOnly(Config.Libs.OpenTelemetry.otelSemconv)

compileOnly(Config.CompileOnly.nopen)
Expand Down
Expand Up @@ -6,22 +6,36 @@
import io.sentry.EventProcessor;
import io.sentry.Hint;
import io.sentry.HubAdapter;
import io.sentry.IHub;
import io.sentry.ISpan;
import io.sentry.Instrumenter;
import io.sentry.SentryEvent;
import io.sentry.SentryLevel;
import io.sentry.SentrySpanStorage;
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 OpenTelemetryLinkErrorEventProcessor implements EventProcessor {

private final @NotNull IHub hub;
private final @NotNull SentrySpanStorage spanStorage = SentrySpanStorage.getInstance();

public OpenTelemetryLinkErrorEventProcessor() {
this(HubAdapter.getInstance());
}

@TestOnly
OpenTelemetryLinkErrorEventProcessor(final @NotNull IHub hub) {
this.hub = hub;
}

@Override
public @Nullable SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) {
if (Instrumenter.OTEL.equals(HubAdapter.getInstance().getOptions().getInstrumenter())) {
final @NotNull Instrumenter instrumenter = hub.getOptions().getInstrumenter();
if (Instrumenter.OTEL.equals(instrumenter)) {
@NotNull final Span otelSpan = Span.current();
@NotNull final String traceId = otelSpan.getSpanContext().getTraceId();
@NotNull final String spanId = otelSpan.getSpanContext().getSpanId();
Expand All @@ -41,8 +55,42 @@ public final class OpenTelemetryLinkErrorEventProcessor implements EventProcesso
null);

event.getContexts().setTrace(spanContext);
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Linking Sentry event %s to span %s created via OpenTelemetry (trace %s).",
event.getEventId(),
spanId,
traceId);
} else {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not linking Sentry event %s to any transaction created via OpenTelemetry as none has been found for span %s (trace %s).",
event.getEventId(),
spanId,
traceId);
}
} else {
hub.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);
}
} else {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not linking Sentry event %s to any transaction created via OpenTelemetry as instrumenter is set to %s.",
event.getEventId(),
instrumenter);
}

return event;
Expand Down
Expand Up @@ -10,7 +10,10 @@
import io.opentelemetry.context.propagation.TextMapSetter;
import io.sentry.Baggage;
import io.sentry.BaggageHeader;
import io.sentry.HubAdapter;
import io.sentry.IHub;
import io.sentry.ISpan;
import io.sentry.SentryLevel;
import io.sentry.SentrySpanStorage;
import io.sentry.SentryTraceHeader;
import io.sentry.exception.InvalidSentryTraceHeaderException;
Expand All @@ -26,6 +29,15 @@ public final class SentryPropagator implements TextMapPropagator {
private static final @NotNull List<String> FIELDS =
Arrays.asList(SentryTraceHeader.SENTRY_TRACE_HEADER, BaggageHeader.BAGGAGE_HEADER);
private final @NotNull SentrySpanStorage spanStorage = SentrySpanStorage.getInstance();
private final @NotNull IHub hub;

public SentryPropagator() {
this(HubAdapter.getInstance());
}

SentryPropagator(final @NotNull IHub hub) {
this.hub = hub;
}

@Override
public Collection<String> fields() {
Expand All @@ -37,10 +49,22 @@ public <C> void inject(final Context context, final C carrier, final TextMapSett
final @NotNull Span otelSpan = Span.fromContext(context);
final @NotNull SpanContext otelSpanContext = otelSpan.getSpanContext();
if (!otelSpanContext.isValid()) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not injecting Sentry tracing information for invalid OpenTelemetry span.");
return;
}
final @Nullable ISpan sentrySpan = spanStorage.get(otelSpanContext.getSpanId());
if (sentrySpan == null || sentrySpan.isNoOp()) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not injecting Sentry tracing information for span %s as no Sentry span has been found or it is a NoOp (trace %s). This might simply mean this is a request to Sentry.",
otelSpanContext.getSpanId(),
otelSpanContext.getTraceId());
return;
}

Expand Down Expand Up @@ -82,8 +106,18 @@ public <C> Context extract(
Span wrappedSpan = Span.wrap(otelSpanContext);
modifiedContext = modifiedContext.with(wrappedSpan);

hub.getOptions()
.getLogger()
.log(SentryLevel.DEBUG, "Continuing Sentry trace %s", sentryTraceHeader.getTraceId());

return modifiedContext;
} catch (InvalidSentryTraceHeaderException e) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.ERROR,
"Unable to extract Sentry tracing information from invalid header.",
e);
return context;
}
}
Expand Down
Expand Up @@ -19,6 +19,7 @@
import io.sentry.ISpan;
import io.sentry.ITransaction;
import io.sentry.Instrumenter;
import io.sentry.SentryLevel;
import io.sentry.SentrySpanStorage;
import io.sentry.SentryTraceHeader;
import io.sentry.SpanId;
Expand Down Expand Up @@ -48,7 +49,7 @@ public SentrySpanProcessor() {
this(HubAdapter.getInstance());
}

SentrySpanProcessor(@NotNull IHub hub) {
SentrySpanProcessor(final @NotNull IHub hub) {
this.hub = hub;
}

Expand All @@ -58,22 +59,44 @@ public void onStart(final @NotNull Context parentContext, final @NotNull ReadWri
return;
}

final @NotNull TraceData traceData = getTraceData(otelSpan, parentContext);

if (isSentryRequest(otelSpan)) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not forwarding OpenTelemetry span %s to Sentry as this looks like a span for a request to Sentry (trace %s).",
traceData.getSpanId(),
traceData.getTraceId());
return;
}

final @NotNull TraceData traceData = getTraceData(otelSpan, parentContext);
final @Nullable ISpan sentryParentSpan =
traceData.getParentSpanId() == null ? null : spanStorage.get(traceData.getParentSpanId());

if (sentryParentSpan != null) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Creating Sentry child span for OpenTelemetry span %s (trace %s). Parent span is %s.",
traceData.getSpanId(),
traceData.getTraceId(),
traceData.getParentSpanId());
final @NotNull Date startDate =
DateUtils.nanosToDate(otelSpan.toSpanData().getStartEpochNanos());
final @NotNull ISpan sentryChildSpan =
sentryParentSpan.startChild(
otelSpan.getName(), otelSpan.getName(), startDate, Instrumenter.OTEL);
spanStorage.store(traceData.getSpanId(), sentryChildSpan);
} else {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Creating Sentry transaction for OpenTelemetry span %s (trace %s).",
traceData.getSpanId(),
traceData.getTraceId());
final @NotNull String transactionName = otelSpan.getName();
final @NotNull TransactionNameSource transactionNameSource = TransactionNameSource.CUSTOM;
final @Nullable String op = otelSpan.getName();
Expand Down Expand Up @@ -124,18 +147,48 @@ public void onEnd(final @NotNull ReadableSpan otelSpan) {
final @Nullable ISpan sentrySpan = spanStorage.removeAndGet(traceData.getSpanId());

if (sentrySpan == null) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Unable to find Sentry span for OpenTelemetry span %s (trace %s). This may simply mean it is a Sentry request.",
adinauer marked this conversation as resolved.
Show resolved Hide resolved
traceData.getSpanId(),
traceData.getTraceId());
return;
}

if (isSentryRequest(otelSpan)) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not forwarding OpenTelemetry span %s to Sentry as this looks like a span for a request to Sentry (trace %s).",
traceData.getSpanId(),
traceData.getTraceId());
return;
}

if (sentrySpan instanceof ITransaction) {
ITransaction sentryTransaction = (ITransaction) sentrySpan;
final @NotNull ITransaction sentryTransaction = (ITransaction) sentrySpan;
updateTransactionWithOtelData(sentryTransaction, otelSpan);
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Finishing Sentry transaction %s for OpenTelemetry span %s (trace %s).",
sentryTransaction.getEventId(),
traceData.getSpanId(),
traceData.getTraceId());
} else {
updateSpanWithOtelData(sentrySpan, otelSpan);
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Finishing Sentry span for OpenTelemetry span %s (trace %s). Parent span is %s.",
traceData.getSpanId(),
traceData.getTraceId(),
traceData.getParentSpanId());
}

final @NotNull SpanStatus sentryStatus = mapOtelStatus(otelSpan);
Expand All @@ -151,15 +204,32 @@ public boolean isEndRequired() {

private boolean ensurePrerequisites(final @NotNull ReadableSpan otelSpan) {
if (!hasSentryBeenInitialized()) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not forwarding OpenTelemetry span to Sentry as Sentry has not yet been initialized.");
return false;
}

if (!Instrumenter.OTEL.equals(hub.getOptions().getInstrumenter())) {
final @NotNull Instrumenter instrumenter = hub.getOptions().getInstrumenter();
if (!Instrumenter.OTEL.equals(instrumenter)) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not forwarding OpenTelemetry span to Sentry as instrumenter has been set to %s.",
instrumenter);
return false;
}

final @NotNull SpanContext otelSpanContext = otelSpan.getSpanContext();
if (!otelSpanContext.isValid()) {
hub.getOptions()
.getLogger()
.log(
SentryLevel.DEBUG,
"Not forwarding OpenTelemetry span to Sentry as the span is invalid.");
return false;
}

Expand Down