Skip to content

Commit 73d19e1

Browse files
authored
POTEL 55 - Fix startChild for span that is not in current OTel Context (#3862)
* Auto config for Spring Boot combined with OTel but without agent * try to cleanup otel classloader * make agent, no agent and agent without auto init work for spring boot * Fix ignored instrumentation for OTel without agent; separate sample for no agent * fix test result upload on CI * automatically detect otel and use OtelSpanFactory * POTEL 54 cleanup * fix non current span start child * changelog
1 parent a619788 commit 73d19e1

File tree

11 files changed

+281
-2
lines changed

11 files changed

+281
-2
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
- `SentrySpanFactoryHolder` has been removed as it is no longer required.
2727
- Add a sample for showcasing Sentry with OpenTelemetry for Spring Boot 3 with our Java agent (`sentry-samples-spring-boot-jakarta-opentelemetry`) ([#3856](https://github.com/getsentry/sentry-java/pull/3828))
2828
- Add a sample for showcasing Sentry with OpenTelemetry for Spring Boot 3 without our Java agent (`sentry-samples-spring-boot-jakarta-opentelemetry-noagent`) ([#3856](https://github.com/getsentry/sentry-java/pull/3856))
29+
- Add a sample for showcasing Sentry with OpenTelemetry (`sentry-samples-console-opentelemetry-noagent`) ([#3856](https://github.com/getsentry/sentry-java/pull/3862))
2930
- Add `globalHubMode` to options ([#3805](https://github.com/getsentry/sentry-java/pull/3805))
3031
- `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`.
3132
- 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.
@@ -42,6 +43,8 @@
4243
- The Sentry OpenTelemetry Java agent now makes sure Sentry `Scopes` storage is initialized even if the agents auto init is disabled ([#3848](https://github.com/getsentry/sentry-java/pull/3848))
4344
- This is required for all integrations to work together with our OpenTelemetry Java agent if its auto init has been disabled and the SDKs init should be used instead.
4445
- Do not ignore certain span origins for OpenTelemetry without agent ([#3856](https://github.com/getsentry/sentry-java/pull/3856))
46+
- Fix `startChild` for span that is not in current OpenTelemetry `Context` ([#3862](https://github.com/getsentry/sentry-java/pull/3862))
47+
- Starting a child span from a transaction that wasn't in the current `Context` lead to multiple transactions being created (one for the transaction and another per span created).
4548
- Add `auto.graphql.graphql22` to ignored span origins when using OpenTelemetry ([#3828](https://github.com/getsentry/sentry-java/pull/3828))
4649
- The Spring Boot 3 WebFlux sample now uses our GraphQL v22 integration ([#3828](https://github.com/getsentry/sentry-java/pull/3828))
4750
- Accept manifest integer values when requiring floating values ([#3823](https://github.com/getsentry/sentry-java/pull/3823))

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ apiValidation {
5151
listOf(
5252
"sentry-samples-android",
5353
"sentry-samples-console",
54+
"sentry-samples-console-opentelemetry-noagent",
5455
"sentry-samples-jul",
5556
"sentry-samples-log4j2",
5657
"sentry-samples-logback",

sentry-opentelemetry/sentry-opentelemetry-bootstrap/api/sentry-opentelemetry-bootstrap.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public abstract interface class io/sentry/opentelemetry/IOtelSpanWrapper : io/se
99
public abstract fun isProfileSampled ()Ljava/lang/Boolean;
1010
public abstract fun setTransactionName (Ljava/lang/String;)V
1111
public abstract fun setTransactionName (Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
12+
public abstract fun storeInContext (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Context;
1213
}
1314

1415
public final class io/sentry/opentelemetry/InternalSemanticAttributes {

sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/IOtelSpanWrapper.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.sentry.opentelemetry;
22

3+
import io.opentelemetry.context.Context;
34
import io.sentry.IScopes;
45
import io.sentry.ISpan;
56
import io.sentry.protocol.MeasurementValue;
@@ -43,4 +44,7 @@ public interface IOtelSpanWrapper extends ISpan {
4344
@ApiStatus.Internal
4445
@NotNull
4546
Map<String, String> getTags();
47+
48+
@NotNull
49+
Context storeInContext(Context context);
4650
}

sentry-opentelemetry/sentry-opentelemetry-bootstrap/src/main/java/io/sentry/opentelemetry/OtelSpanFactory.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public OtelSpanFactory() {
100100
TraceFlags.getSampled(),
101101
TraceState.getDefault());
102102
final @NotNull Span wrappedSpan = Span.wrap(otelSpanContext);
103-
spanBuilder.setParent(Context.root().with(wrappedSpan));
103+
spanBuilder.setParent(wrappedSpan.storeInContext(Context.current()));
104104
} else {
105105
final @NotNull io.opentelemetry.api.trace.SpanContext otelSpanContext =
106106
io.opentelemetry.api.trace.SpanContext.createFromRemoteParent(
@@ -109,7 +109,12 @@ public OtelSpanFactory() {
109109
TraceFlags.getSampled(),
110110
TraceState.getDefault());
111111
final @NotNull Span wrappedSpan = Span.wrap(otelSpanContext);
112-
spanBuilder.setParent(Context.root().with(wrappedSpan));
112+
spanBuilder.setParent(wrappedSpan.storeInContext(Context.current()));
113+
}
114+
} else {
115+
if (parentSpan instanceof IOtelSpanWrapper) {
116+
IOtelSpanWrapper parentSpanWrapper = (IOtelSpanWrapper) parentSpan;
117+
spanBuilder.setParent(parentSpanWrapper.storeInContext(Context.current()));
113118
}
114119
}
115120

sentry-opentelemetry/sentry-opentelemetry-core/api/sentry-opentelemetry-core.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public final class io/sentry/opentelemetry/OtelSpanWrapper : io/sentry/opentelem
9090
public fun startChild (Ljava/lang/String;Ljava/lang/String;Lio/sentry/SentryDate;Lio/sentry/Instrumenter;)Lio/sentry/ISpan;
9191
public fun startChild (Ljava/lang/String;Ljava/lang/String;Lio/sentry/SentryDate;Lio/sentry/Instrumenter;Lio/sentry/SpanOptions;)Lio/sentry/ISpan;
9292
public fun startChild (Ljava/lang/String;Ljava/lang/String;Lio/sentry/SpanOptions;)Lio/sentry/ISpan;
93+
public fun storeInContext (Lio/opentelemetry/context/Context;)Lio/opentelemetry/context/Context;
9394
public fun toBaggageHeader (Ljava/util/List;)Lio/sentry/BaggageHeader;
9495
public fun toSentryTrace ()Lio/sentry/SentryTraceHeader;
9596
public fun traceContext ()Lio/sentry/TraceContext;

sentry-opentelemetry/sentry-opentelemetry-core/src/main/java/io/sentry/opentelemetry/OtelSpanWrapper.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.sentry.opentelemetry;
22

33
import io.opentelemetry.api.trace.Span;
4+
import io.opentelemetry.context.Context;
45
import io.opentelemetry.context.Scope;
56
import io.opentelemetry.sdk.trace.ReadWriteSpan;
67
import io.sentry.Baggage;
@@ -468,6 +469,16 @@ public Map<String, MeasurementValue> getMeasurements() {
468469
return scopes;
469470
}
470471

472+
@Override
473+
public @NotNull Context storeInContext(Context context) {
474+
final @Nullable ReadWriteSpan otelSpan = getSpan();
475+
if (otelSpan != null) {
476+
return otelSpan.storeInContext(context);
477+
} else {
478+
return context;
479+
}
480+
}
481+
471482
@SuppressWarnings("MustBeClosedChecker")
472483
@ApiStatus.Internal
473484
@Override
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Sentry Sample Console
2+
3+
Sample application showing how to use Sentry manually without any framework integration.
4+
5+
## How to run?
6+
7+
To see events triggered in this sample application in your Sentry dashboard, go to `src/main/java/io/sentry/samples/console/Main.java` and replace the test DSN with your own DSN.
8+
9+
Then, execute a command from the module directory:
10+
11+
```
12+
../../gradlew run
13+
```
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
plugins {
2+
java
3+
application
4+
id(Config.QualityPlugins.gradleVersions)
5+
}
6+
7+
application {
8+
mainClass.set("io.sentry.samples.console.Main")
9+
}
10+
11+
configure<JavaPluginExtension> {
12+
sourceCompatibility = JavaVersion.VERSION_1_8
13+
targetCompatibility = JavaVersion.VERSION_1_8
14+
}
15+
16+
dependencies {
17+
implementation(projects.sentry)
18+
19+
implementation(projects.sentryOpentelemetry.sentryOpentelemetryAgentcustomization)
20+
implementation(projects.sentryOpentelemetry.sentryOpentelemetryBootstrap)
21+
implementation(Config.Libs.OpenTelemetry.otelSdk)
22+
implementation(Config.Libs.OpenTelemetry.otelExtensionAutoconfigure)
23+
implementation(Config.Libs.OpenTelemetry.otelSemconv)
24+
implementation(Config.Libs.OpenTelemetry.otelSemconvIncubating)
25+
}
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
package io.sentry.samples.console;
2+
3+
import io.opentelemetry.api.GlobalOpenTelemetry;
4+
import io.opentelemetry.api.trace.Span;
5+
import io.opentelemetry.api.trace.StatusCode;
6+
import io.opentelemetry.context.Scope;
7+
import io.sentry.Breadcrumb;
8+
import io.sentry.EventProcessor;
9+
import io.sentry.Hint;
10+
import io.sentry.ISentryLifecycleToken;
11+
import io.sentry.ISpan;
12+
import io.sentry.ITransaction;
13+
import io.sentry.Sentry;
14+
import io.sentry.SentryEvent;
15+
import io.sentry.SentryLevel;
16+
import io.sentry.SpanStatus;
17+
import io.sentry.protocol.Message;
18+
import io.sentry.protocol.User;
19+
import java.util.Collections;
20+
21+
public class Main {
22+
23+
public static void main(String[] args) throws InterruptedException {
24+
Sentry.init(
25+
options -> {
26+
// NOTE: Replace the test DSN below with YOUR OWN DSN to see the events from this app in
27+
// your Sentry project/dashboard
28+
options.setDsn(
29+
"https://[email protected]/5428563");
30+
31+
// All events get assigned to the release. See more at
32+
// https://docs.sentry.io/workflow/releases/
33+
options.setRelease("[email protected]+1");
34+
35+
// Modifications to event before it goes out. Could replace the event altogether
36+
options.setBeforeSend(
37+
(event, hint) -> {
38+
// Drop an event altogether:
39+
if (event.getTag("SomeTag") != null) {
40+
return null;
41+
}
42+
return event;
43+
});
44+
45+
options.setBeforeSendTransaction(
46+
(transaction, hint) -> {
47+
// Drop a transaction:
48+
if (transaction.getTag("SomeTransactionTag") != null) {
49+
return null;
50+
}
51+
52+
return transaction;
53+
});
54+
55+
// Allows inspecting and modifying, returning a new or simply rejecting (returning null)
56+
options.setBeforeBreadcrumb(
57+
(breadcrumb, hint) -> {
58+
// Don't add breadcrumbs with message containing:
59+
if (breadcrumb.getMessage() != null
60+
&& breadcrumb.getMessage().contains("bad breadcrumb")) {
61+
return null;
62+
}
63+
return breadcrumb;
64+
});
65+
66+
// Configure the background worker which sends events to sentry:
67+
// Wait up to 5 seconds before shutdown while there are events to send.
68+
options.setShutdownTimeoutMillis(5000);
69+
70+
// Enable SDK logging with Debug level
71+
options.setDebug(true);
72+
// To change the verbosity, use:
73+
// By default it's DEBUG.
74+
// options.setDiagnosticLevel(
75+
// SentryLevel
76+
// .ERROR); // A good option to have SDK debug log in prod is to use
77+
// only level
78+
// ERROR here.
79+
options.setEnablePrettySerializationOutput(false);
80+
81+
// Exclude frames from some packages from being "inApp" so are hidden by default in Sentry
82+
// UI:
83+
options.addInAppExclude("org.jboss");
84+
85+
// Include frames from our package
86+
options.addInAppInclude("io.sentry.samples");
87+
88+
// Performance configuration options
89+
// Set what percentage of traces should be collected
90+
options.setTracesSampleRate(1.0); // set 0.5 to send 50% of traces
91+
92+
// Determine traces sample rate based on the sampling context
93+
// options.setTracesSampler(
94+
// context -> {
95+
// // only 10% of transactions with "/product" prefix will be collected
96+
// if (!context.getTransactionContext().getName().startsWith("/products"))
97+
// {
98+
// return 0.1;
99+
// } else {
100+
// return 0.5;
101+
// }
102+
// });
103+
});
104+
105+
Sentry.addBreadcrumb(
106+
"A 'bad breadcrumb' that will be rejected because of 'BeforeBreadcrumb callback above.'");
107+
108+
// Data added to the root scope (no PushScope called up to this point)
109+
// The modifications done here will affect all events sent and will propagate to child scopes.
110+
Sentry.configureScope(
111+
scope -> {
112+
scope.addEventProcessor(new SomeEventProcessor());
113+
114+
scope.setExtra("SomeExtraInfo", "Some value for extra info");
115+
});
116+
117+
// Configures a scope which is only valid within the callback
118+
Sentry.withScope(
119+
scope -> {
120+
scope.setLevel(SentryLevel.FATAL);
121+
scope.setTransaction("main");
122+
123+
// This message includes the data set to the scope in this block:
124+
Sentry.captureMessage("Fatal message!");
125+
});
126+
127+
// Only data added to the scope on `configureScope` above is included.
128+
Sentry.captureMessage("Some warning!", SentryLevel.WARNING);
129+
130+
// Sending exception:
131+
Exception exception = new RuntimeException("Some error!");
132+
Sentry.captureException(exception);
133+
134+
// An event with breadcrumb and user data
135+
SentryEvent evt = new SentryEvent();
136+
Message msg = new Message();
137+
msg.setMessage("Detailed event");
138+
evt.setMessage(msg);
139+
evt.addBreadcrumb("Breadcrumb directly to the event");
140+
User user = new User();
141+
user.setUsername("some@user");
142+
evt.setUser(user);
143+
// Group all events with the following fingerprint:
144+
evt.setFingerprints(Collections.singletonList("NewClientDebug"));
145+
evt.setLevel(SentryLevel.DEBUG);
146+
Sentry.captureEvent(evt);
147+
148+
int count = 10;
149+
for (int i = 0; i < count; i++) {
150+
String messageContent = "%d of %d items we'll wait to flush to Sentry!";
151+
Message message = new Message();
152+
message.setMessage(messageContent);
153+
message.setFormatted(String.format(messageContent, i, count));
154+
SentryEvent event = new SentryEvent();
155+
event.setMessage(message);
156+
157+
final Hint hint = new Hint();
158+
hint.set("level", SentryLevel.DEBUG);
159+
Sentry.captureEvent(event, hint);
160+
}
161+
162+
// Performance feature
163+
//
164+
// Transactions collect execution time of the piece of code that's executed between the start
165+
// and finish of transaction.
166+
ITransaction transaction = Sentry.startTransaction("transaction name", "op");
167+
// Transactions can contain one or more Spans
168+
ISpan outerSpan = transaction.startChild("child");
169+
Thread.sleep(100);
170+
// Spans create a tree structure. Each span can have one ore more spans inside.
171+
ISpan innerSpan = outerSpan.startChild("jdbc", "select * from product where id = :id");
172+
innerSpan.setStatus(SpanStatus.OK);
173+
Thread.sleep(300);
174+
// Finish the span and mark the end time of the span execution.
175+
// Note: finishing spans does not send them to Sentry
176+
innerSpan.finish();
177+
try (ISentryLifecycleToken outerScope = outerSpan.makeCurrent()) {
178+
Span otelSpan =
179+
GlobalOpenTelemetry.get()
180+
.getTracer("demoTracer", "1.0.0")
181+
.spanBuilder("otelSpan")
182+
.startSpan();
183+
try (Scope innerScope = otelSpan.makeCurrent()) {
184+
otelSpan.setAttribute("otel-attribute", "attribute-value");
185+
Thread.sleep(150);
186+
otelSpan.setStatus(StatusCode.OK);
187+
} finally {
188+
otelSpan.end();
189+
}
190+
}
191+
// Every SentryEvent reported during the execution of the transaction or a span, will have trace
192+
// context attached
193+
Sentry.captureMessage("this message is connected to the outerSpan");
194+
outerSpan.finish();
195+
// marks transaction as finished and sends it together with all child spans to Sentry
196+
transaction.finish();
197+
198+
// All events that have not been sent yet are being flushed on JVM exit. Events can be also
199+
// flushed manually:
200+
// Sentry.close();
201+
}
202+
203+
private static class SomeEventProcessor implements EventProcessor {
204+
@Override
205+
public SentryEvent process(SentryEvent event, Hint hint) {
206+
// Here you can modify the event as you need
207+
if (event.getLevel() != null && event.getLevel().ordinal() > SentryLevel.INFO.ordinal()) {
208+
event.addBreadcrumb(new Breadcrumb("Processed by " + SomeEventProcessor.class));
209+
}
210+
211+
return event;
212+
}
213+
}
214+
}

0 commit comments

Comments
 (0)