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 50 - Auto config for Spring Boot combined with OTel but without agent #3846

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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 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}"
adinauer marked this conversation as resolved.
Show resolved Hide resolved

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");
adinauer marked this conversation as resolved.
Show resolved Hide resolved
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());
adinauer marked this conversation as resolved.
Show resolved Hide resolved
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)
adinauer marked this conversation as resolved.
Show resolved Hide resolved
implementation("io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter")
implementation(Config.Libs.springBoot3StarterOpenTelemetry)
adinauer marked this conversation as resolved.
Show resolved Hide resolved
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)
adinauer marked this conversation as resolved.
Show resolved Hide resolved
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);
adinauer marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
package io.sentry.samples.spring.boot.jakarta;

import static io.sentry.quartz.SentryJobListener.SENTRY_SLUG_KEY;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Tracer;
import io.sentry.samples.spring.boot.jakarta.quartz.SampleJob;
import java.util.Collections;
import org.quartz.JobDetail;
import org.quartz.SimpleTrigger;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
Expand All @@ -42,37 +31,39 @@ RestClient restClient(RestClient.Builder builder) {
return builder.build();
}

@Bean
public JobDetailFactoryBean jobDetail() {
JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
jobDetailFactory.setJobClass(SampleJob.class);
jobDetailFactory.setDurability(true);
jobDetailFactory.setJobDataAsMap(
Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail"));
return jobDetailFactory;
}

@Bean
public SimpleTriggerFactoryBean trigger(JobDetail job) {
SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
trigger.setJobDetail(job);
trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes
trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
trigger.setJobDataAsMap(
Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger"));
return trigger;
}
// @Bean
// public JobDetailFactoryBean jobDetail() {
// JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
// jobDetailFactory.setJobClass(SampleJob.class);
// jobDetailFactory.setDurability(true);
// jobDetailFactory.setJobDataAsMap(
// Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_job_detail"));
// return jobDetailFactory;
// }
//
// @Bean
// public SimpleTriggerFactoryBean trigger(JobDetail job) {
// SimpleTriggerFactoryBean trigger = new SimpleTriggerFactoryBean();
// trigger.setJobDetail(job);
// trigger.setRepeatInterval(2 * 60 * 1000); // every two minutes
// trigger.setRepeatCount(SimpleTrigger.REPEAT_INDEFINITELY);
// trigger.setJobDataAsMap(
// Collections.singletonMap(SENTRY_SLUG_KEY, "monitor_slug_simple_trigger"));
// return trigger;
// }
//
// @Bean
// public CronTriggerFactoryBean cronTrigger(JobDetail job) {
// CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
// trigger.setJobDetail(job);
// trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes
// return trigger;
// }
adinauer marked this conversation as resolved.
Show resolved Hide resolved

@Bean
public CronTriggerFactoryBean cronTrigger(JobDetail job) {
CronTriggerFactoryBean trigger = new CronTriggerFactoryBean();
trigger.setJobDetail(job);
trigger.setCronExpression("0 0/5 * ? * *"); // every five minutes
return trigger;
}

@Bean
public Tracer getTracer() {
return GlobalOpenTelemetry.getTracer("tracerForSpringBootDemo");
}
// @Bean
// public Tracer getTracer(OpenTelemetry openTelemetry) {
// GlobalOpenTelemetry.set(openTelemetry);
// return openTelemetry.getTracer("tracerForSpringBootDemo");
//// return GlobalOpenTelemetry.getTracer("tracerForSpringBootDemo");
// }
adinauer marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading