Skip to content

Commit

Permalink
POTEL 50 - Auto config for Spring Boot combined with OTel but without…
Browse files Browse the repository at this point in the history
… agent (#3846)

* Auto config for Spring Boot combined with OTel but without agent

* changelog
  • Loading branch information
adinauer authored Nov 11, 2024
1 parent 2aa37fe commit ab7e6fd
Show file tree
Hide file tree
Showing 28 changed files with 299 additions and 121 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

### Features

- Spring Boot now automatically detects if OpenTelemetry is available and makes use of it ([#3846](https://github.com/getsentry/sentry-java/pull/3846))
- This is only enabled if there is no OpenTelemetry agent available
- We prefer to use the OpenTelemetry agent as it offers more auto instrumentation
- In some cases the OpenTelemetry agent cannot be used, please see https://opentelemetry.io/docs/zero-code/java/spring-boot-starter/ for more details on when to prefer the Agent and when the Spring Boot starter makes more sense.
- Add `globalHubMode` to options ([#3805](https://github.com/getsentry/sentry-java/pull/3805))
- `globalHubMode` used to only be a param on `Sentry.init`. To make it easier to be used in e.g. Desktop environments, we now additionally added it as an option on SentryOptions that can also be set via `sentry.properties`.
- If both the param on `Sentry.init` and the option are set, the option will win. By default the option is set to `null` meaning whatever is passed to `Sentry.init` takes effect.
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 @@ -94,6 +94,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
@@ -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
@@ -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 @@ -13,7 +12,6 @@
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 +27,27 @@
public final class SentryAutoConfigurationCustomizerProvider
implements AutoConfigurationCustomizerProvider {

public static volatile boolean skipInit = false;

@Override
public void customize(AutoConfigurationCustomizer autoConfiguration) {
System.out.println("hello from agent");
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()) {
System.out.println("hello from before agent init");
Sentry.init(
options -> {
System.out.println("hello from agent init options config block");
options.setEnableExternalConfiguration(true);
options.setInitPriority(InitPriority.HIGH);
options.setIgnoredSpanOrigins(SpanUtils.ignoredSpanOriginsForOpenTelemetry());
options.setSpanFactory(spanFactory);
OpenTelemetryUtil.applyOpenTelemetryOptions(options);
//
// options.setIgnoredSpanOrigins(SpanUtils.ignoredSpanOriginsForOpenTelemetry());
// options.setSpanFactory(spanFactory);
final @Nullable SdkVersion sdkVersion = createSdkVersion(options, versionInfoHolder);
if (sdkVersion != null) {
options.setSdkVersion(sdkVersion);
Expand All @@ -76,6 +70,9 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
}

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
Expand Up @@ -7,6 +7,7 @@ public final class io/sentry/opentelemetry/OtelContextScopesStorage : io/sentry/
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;
}

Expand All @@ -20,6 +21,7 @@ public final class io/sentry/opentelemetry/OtelSpanContext : io/sentry/SpanConte

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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static io.sentry.opentelemetry.SentryOtelKeys.SENTRY_SCOPES_KEY;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.Scope;
import io.sentry.IScopes;
import io.sentry.IScopesStorage;
Expand All @@ -15,6 +16,22 @@
@SuppressWarnings("MustBeClosedChecker")
public final class OtelContextScopesStorage implements IScopesStorage {

@Override
public void init() {
System.out.println("hello from OtelContextScopesStorage init");
/**
* 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()));
}

@Override
public @NotNull ISentryLifecycleToken set(@Nullable IScopes scopes) {
final Context context = Context.current();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package io.sentry.opentelemetry;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.TraceFlags;
import io.opentelemetry.api.trace.TraceState;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.api.trace.TracerProvider;
import io.opentelemetry.context.Context;
import io.sentry.Baggage;
import io.sentry.BuildConfig;
import io.sentry.IScopes;
import io.sentry.ISpan;
import io.sentry.ISpanFactory;
Expand All @@ -33,6 +36,15 @@
public final class OtelSpanFactory implements ISpanFactory {

private final @NotNull SentryWeakSpanStorage storage = SentryWeakSpanStorage.getInstance();
private final @Nullable OpenTelemetry openTelemetry;

public OtelSpanFactory(final @Nullable OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
}

public OtelSpanFactory() {
this(null);
}

@Override
public @NotNull ITransaction createTransaction(
Expand Down Expand Up @@ -145,7 +157,14 @@ public final class OtelSpanFactory implements ISpanFactory {
}

private @NotNull Tracer getTracer() {
return GlobalOpenTelemetry.getTracer(
"sentry-instrumentation-scope-name", "sentry-instrumentation-scope-version");
return getTracerProvider().get("sentry-opentelemetry", BuildConfig.VERSION_NAME);
}

private @NotNull TracerProvider getTracerProvider() {
System.out.println("hello from " + toString());
if (openTelemetry != null) {
return openTelemetry.getTracerProvider();
}
return GlobalOpenTelemetry.getTracerProvider();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,15 @@
import io.opentelemetry.context.Context;
import io.opentelemetry.context.ContextStorage;
import io.opentelemetry.context.Scope;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

@ApiStatus.Internal
public final class SentryContextStorage implements ContextStorage {
private final @NotNull Logger logger = Logger.getLogger(SentryContextStorage.class.getName());

private final @NotNull ContextStorage contextStorage;

public SentryContextStorage(final @NotNull ContextStorage contextStorage) {
this.contextStorage = contextStorage;
logger.log(Level.SEVERE, "SentryContextStorage ctor called");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ dependencies {
implementation(projects.sentryLogback)
implementation(projects.sentryGraphql22)
implementation(projects.sentryQuartz)
implementation(Config.Libs.OpenTelemetry.otelSdk)
// implementation(Config.Libs.OpenTelemetry.otelSdk)
implementation("io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter")
implementation(Config.Libs.springBoot3StarterOpenTelemetry)
implementation(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap)
implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization)

// database query tracing
implementation(projects.sentryJdbc)
Expand All @@ -67,6 +71,12 @@ dependencies {
testImplementation(Config.Libs.apolloKotlin)
}

dependencyManagement {
imports {
mavenBom("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.7.0")
}
}

configure<SourceSetContainer> {
test {
java.srcDir("src/test/java")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import io.sentry.spring.jakarta.tracing.SentryTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

/**
Expand All @@ -18,7 +17,7 @@ public class CustomJob {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomJob.class);

@SentryCheckIn("monitor_slug_1")
@Scheduled(fixedRate = 3 * 60 * 1000L)
// @Scheduled(fixedRate = 3 * 60 * 1000L)
void execute() throws InterruptedException {
LOGGER.info("Executing scheduled job");
Thread.sleep(2000L);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package io.sentry.samples.spring.boot.jakarta;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.sentry.ISpan;
import io.sentry.Sentry;
import org.jetbrains.annotations.NotNull;
Expand All @@ -19,19 +20,27 @@
@RequestMapping("/person/")
public class PersonController {
private final PersonService personService;
private final Tracer tracer;
private final OpenTelemetry openTelemetry;
private static final Logger LOGGER = LoggerFactory.getLogger(PersonController.class);

public PersonController(PersonService personService, Tracer tracer) {
public PersonController(PersonService personService, OpenTelemetry openTelemetry) {
this.personService = personService;
this.tracer = tracer;
this.openTelemetry = openTelemetry;
}

@GetMapping("{id}")
@WithSpan("personSpanThroughOtelAnnotation")
Person person(@PathVariable Long id) {
Span span = tracer.spanBuilder("spanCreatedThroughOtelApi").startSpan();
ISpan annotationSpan = Sentry.getSpan();
System.out.println(annotationSpan);
Span span =
openTelemetry
.getTracer("tracerForSpringBootDemo")
.spanBuilder("spanCreatedThroughOtelApi")
.startSpan();
try (final @NotNull Scope spanScope = span.makeCurrent()) {
ISpan sentrySpan = Sentry.getSpan().startChild("spanCreatedThroughSentryApi");
ISpan currentSpan = Sentry.getSpan();
ISpan sentrySpan = currentSpan.startChild("spanCreatedThroughSentryApi");
try {
LOGGER.error("Trying person with id={}", id, new RuntimeException("error while loading"));
throw new IllegalArgumentException("Something went wrong [id=" + id + "]");
Expand All @@ -45,7 +54,11 @@ Person person(@PathVariable Long id) {

@PostMapping
Person create(@RequestBody Person person) {
Span span = tracer.spanBuilder("spanCreatedThroughOtelApi").startSpan();
Span span =
openTelemetry
.getTracer("tracerForSpringBootDemo")
.spanBuilder("spanCreatedThroughOtelApi")
.startSpan();
try (final @NotNull Scope spanScope = span.makeCurrent()) {
ISpan sentrySpan = Sentry.getSpan().startChild("spanCreatedThroughSentryApi");
try {
Expand Down
Loading

0 comments on commit ab7e6fd

Please sign in to comment.