Skip to content

Commit cfaca0b

Browse files
authored
Merge 9cef931 into 6b344be
2 parents 6b344be + 9cef931 commit cfaca0b

File tree

20 files changed

+540
-13
lines changed

20 files changed

+540
-13
lines changed

buildSrc/src/main/java/Config.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ object Config {
133133
val apolloKotlin = "com.apollographql.apollo3:apollo-runtime:3.3.0"
134134

135135
val otelVersion = "1.19.0"
136+
val otelAlphaVersion = "1.19.0-alpha"
136137
val otelJavaagentVersion = "1.19.2"
137138
val otelJavaagentAlphaVersion = "1.19.2-alpha"
138139
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,22 @@ public final class io/sentry/opentelemetry/SentrySpanProcessor : io/opentelemetr
66
public fun onStart (Lio/opentelemetry/context/Context;Lio/opentelemetry/sdk/trace/ReadWriteSpan;)V
77
}
88

9+
public final class io/sentry/opentelemetry/SpanDescription {
10+
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lio/sentry/protocol/TransactionNameSource;)V
11+
public fun getDescription ()Ljava/lang/String;
12+
public fun getOp ()Ljava/lang/String;
13+
public fun getTransactionNameSource ()Lio/sentry/protocol/TransactionNameSource;
14+
}
15+
16+
public final class io/sentry/opentelemetry/SpanDescriptionExtractor {
17+
public fun <init> ()V
18+
public fun extractSpanDescription (Lio/opentelemetry/sdk/trace/ReadableSpan;)Lio/sentry/opentelemetry/SpanDescription;
19+
}
20+
21+
public final class io/sentry/opentelemetry/TraceData {
22+
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
23+
public fun getParentSpanId ()Ljava/lang/String;
24+
public fun getSpanId ()Ljava/lang/String;
25+
public fun getTraceId ()Ljava/lang/String;
26+
}
27+

sentry-opentelemetry/sentry-opentelemetry-core/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dependencies {
2222
compileOnly(projects.sentry)
2323

2424
compileOnly("io.opentelemetry:opentelemetry-sdk:${Config.Libs.otelVersion}")
25+
compileOnly("io.opentelemetry:opentelemetry-semconv:${Config.Libs.otelAlphaVersion}")
2526

2627
compileOnly(Config.CompileOnly.nopen)
2728
errorprone(Config.CompileOnly.nopenChecker)
Lines changed: 211 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,95 @@
11
package io.sentry.opentelemetry;
22

3+
import io.opentelemetry.api.trace.SpanContext;
4+
import io.opentelemetry.api.trace.SpanKind;
5+
import io.opentelemetry.api.trace.StatusCode;
36
import io.opentelemetry.context.Context;
47
import io.opentelemetry.sdk.trace.ReadWriteSpan;
58
import io.opentelemetry.sdk.trace.ReadableSpan;
69
import io.opentelemetry.sdk.trace.SpanProcessor;
10+
import io.opentelemetry.sdk.trace.data.SpanData;
11+
import io.opentelemetry.sdk.trace.data.StatusData;
12+
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
13+
import io.sentry.DateUtils;
14+
import io.sentry.DsnUtil;
15+
import io.sentry.ISpan;
16+
import io.sentry.ITransaction;
17+
import io.sentry.Instrumenter;
718
import io.sentry.Sentry;
19+
import io.sentry.SpanId;
20+
import io.sentry.SpanStatus;
21+
import io.sentry.TransactionContext;
22+
import io.sentry.TransactionOptions;
23+
import io.sentry.protocol.SentryId;
24+
import io.sentry.protocol.TransactionNameSource;
25+
import java.util.Arrays;
26+
import java.util.Date;
27+
import java.util.HashMap;
28+
import java.util.List;
29+
import java.util.Map;
30+
import java.util.concurrent.ConcurrentHashMap;
31+
import org.jetbrains.annotations.NotNull;
32+
import org.jetbrains.annotations.Nullable;
833

9-
@SuppressWarnings("CatchAndPrintStackTrace")
1034
public final class SentrySpanProcessor implements SpanProcessor {
1135

36+
private final @NotNull Map<String, ISpan> spans = new ConcurrentHashMap<>();
37+
private final @NotNull List<SpanKind> spanKindsConsideredForSentryRequests =
38+
Arrays.asList(SpanKind.CLIENT, SpanKind.INTERNAL);
39+
private final @NotNull SpanDescriptionExtractor spanDescriptionExtractor =
40+
new SpanDescriptionExtractor();
41+
1242
@Override
13-
public void onStart(Context parentContext, ReadWriteSpan span) {
14-
System.out.println(
15-
"hello from onStart " + Thread.currentThread().getId() + Sentry.getCurrentHub().toString());
16-
// TODO start
43+
public void onStart(final @NotNull Context parentContext, final @NotNull ReadWriteSpan otelSpan) {
44+
if (!hasSentryBeenInitialized()) {
45+
return;
46+
}
47+
48+
if (!Instrumenter.OTEL.equals(Sentry.getCurrentHub().getOptions().getInstrumenter())) {
49+
return;
50+
}
51+
52+
final @NotNull SpanContext otelSpanContext = otelSpan.getSpanContext();
53+
if (!otelSpanContext.isValid()) {
54+
return;
55+
}
56+
57+
if (isSentryRequest(otelSpan)) {
58+
return;
59+
}
60+
61+
final @NotNull TraceData traceData = getTraceData(otelSpan);
62+
final @Nullable ISpan sentryParentSpan =
63+
traceData.getParentSpanId() == null ? null : spans.get(traceData.getParentSpanId());
64+
65+
if (sentryParentSpan != null) {
66+
System.out.println("found a parent span: " + traceData.getParentSpanId());
67+
final @NotNull Date startDate =
68+
DateUtils.nanosToDate(otelSpan.toSpanData().getStartEpochNanos());
69+
final @NotNull ISpan sentryChildSpan =
70+
sentryParentSpan.startChild(
71+
otelSpan.getName(), otelSpan.getName(), startDate, Instrumenter.OTEL);
72+
spans.put(traceData.getSpanId(), sentryChildSpan);
73+
} else {
74+
TransactionContext transactionContext =
75+
new TransactionContext(
76+
otelSpan.getName(),
77+
otelSpan.getName(),
78+
new SentryId(traceData.getTraceId()),
79+
new SpanId(traceData.getSpanId()),
80+
TransactionNameSource.CUSTOM,
81+
null,
82+
null,
83+
null);
84+
transactionContext.setInstrumenter(Instrumenter.OTEL);
85+
86+
TransactionOptions transactionOptions = new TransactionOptions();
87+
transactionOptions.setStartTimestamp(
88+
DateUtils.nanosToDate(otelSpan.toSpanData().getStartEpochNanos()));
89+
90+
ISpan sentryTransaction = Sentry.startTransaction(transactionContext, transactionOptions);
91+
spans.put(traceData.getSpanId(), sentryTransaction);
92+
}
1793
}
1894

1995
@Override
@@ -22,14 +98,140 @@ public boolean isStartRequired() {
2298
}
2399

24100
@Override
25-
public void onEnd(ReadableSpan span) {
26-
System.out.println(
27-
"hello from onEnd" + Thread.currentThread().getId() + Sentry.getCurrentHub().toString());
28-
// TODO end
101+
public void onEnd(final @NotNull ReadableSpan otelSpan) {
102+
if (!hasSentryBeenInitialized()) {
103+
return;
104+
}
105+
106+
if (!Instrumenter.OTEL.equals(Sentry.getCurrentHub().getOptions().getInstrumenter())) {
107+
return;
108+
}
109+
110+
final @NotNull SpanContext otelSpanContext = otelSpan.getSpanContext();
111+
if (!otelSpanContext.isValid()) {
112+
return;
113+
}
114+
115+
final @NotNull TraceData traceData = getTraceData(otelSpan);
116+
final @Nullable ISpan sentrySpan = spans.remove(traceData.getSpanId());
117+
118+
if (sentrySpan == null) {
119+
return;
120+
}
121+
122+
if (isSentryRequest(otelSpan)) {
123+
return;
124+
}
125+
126+
if (sentrySpan instanceof ITransaction) {
127+
ITransaction sentryTransaction = (ITransaction) sentrySpan;
128+
updateTransactionWithOtelData(sentryTransaction, otelSpan);
129+
} else {
130+
updateSpanWithOtelData(sentrySpan, otelSpan);
131+
}
132+
133+
final @NotNull SpanStatus sentryStatus = mapOtelStatus(otelSpan);
134+
final @NotNull Date endTimestamp =
135+
DateUtils.nanosToDate(otelSpan.toSpanData().getEndEpochNanos());
136+
sentrySpan.finish(sentryStatus, endTimestamp);
29137
}
30138

31139
@Override
32140
public boolean isEndRequired() {
33141
return true;
34142
}
143+
144+
private @NotNull TraceData getTraceData(final @NotNull ReadableSpan otelSpan) {
145+
final @NotNull SpanContext otelSpanContext = otelSpan.getSpanContext();
146+
final @NotNull String otelSpanId = otelSpanContext.getSpanId();
147+
final @NotNull String otelParentSpanIdMaybeInvalid =
148+
otelSpan.getParentSpanContext().getSpanId();
149+
final @NotNull String otelTraceId = otelSpanContext.getTraceId();
150+
final @Nullable String otelParentSpanId =
151+
io.opentelemetry.api.trace.SpanId.isValid(otelParentSpanIdMaybeInvalid)
152+
? otelParentSpanIdMaybeInvalid
153+
: null;
154+
155+
// TODO read parentSampled and baggage from context set by propagator
156+
157+
return new TraceData(otelTraceId, otelSpanId, otelParentSpanId);
158+
}
159+
160+
private boolean isSentryRequest(final @NotNull ReadableSpan otelSpan) {
161+
final @NotNull SpanKind kind = otelSpan.getKind();
162+
if (!spanKindsConsideredForSentryRequests.contains(kind)) {
163+
return false;
164+
}
165+
166+
final @Nullable String httpUrl = otelSpan.getAttribute(SemanticAttributes.HTTP_URL);
167+
return DsnUtil.urlContainsDsnHost(Sentry.getCurrentHub().getOptions(), httpUrl);
168+
}
169+
170+
private void updateTransactionWithOtelData(
171+
final @NotNull ITransaction sentryTransaction, final @NotNull ReadableSpan otelSpan) {
172+
final @NotNull SpanDescription spanDescription =
173+
spanDescriptionExtractor.extractSpanDescription(otelSpan);
174+
sentryTransaction.setOperation(spanDescription.getOp());
175+
sentryTransaction.setName(
176+
spanDescription.getDescription(), spanDescription.getTransactionNameSource());
177+
178+
final @NotNull Map<String, Object> otelContext = toOtelContext(otelSpan);
179+
System.out.println(otelContext);
180+
// TODO set otel context on transaction
181+
}
182+
183+
private @NotNull Map<String, Object> toOtelContext(final @NotNull ReadableSpan otelSpan) {
184+
final @NotNull SpanData spanData = otelSpan.toSpanData();
185+
final @NotNull Map<String, Object> context = new HashMap<>();
186+
187+
context.put("attributes", spanData.getAttributes().asMap());
188+
context.put("resource", spanData.getResource().getAttributes().asMap());
189+
190+
return context;
191+
}
192+
193+
private void updateSpanWithOtelData(
194+
final @NotNull ISpan sentrySpan, final @NotNull ReadableSpan otelSpan) {
195+
final @NotNull SpanData spanData = otelSpan.toSpanData();
196+
197+
sentrySpan.setData("otel.kind", otelSpan.getKind());
198+
199+
spanData
200+
.getAttributes()
201+
.forEach(
202+
(attributeKey, value) -> {
203+
if (value != null) {
204+
sentrySpan.setData(attributeKey.getKey(), value);
205+
}
206+
});
207+
208+
final @NotNull SpanDescription spanDescription =
209+
spanDescriptionExtractor.extractSpanDescription(otelSpan);
210+
sentrySpan.setOperation(spanDescription.getOp());
211+
sentrySpan.setDescription(spanDescription.getDescription());
212+
}
213+
214+
private SpanStatus mapOtelStatus(final @NotNull ReadableSpan otelSpan) {
215+
final @NotNull SpanData otelSpanData = otelSpan.toSpanData();
216+
final @NotNull StatusData otelStatus = otelSpanData.getStatus();
217+
final @NotNull StatusCode otelStatusCode = otelStatus.getStatusCode();
218+
219+
if (StatusCode.OK.equals(otelStatusCode) || StatusCode.UNSET.equals(otelStatusCode)) {
220+
return SpanStatus.OK;
221+
}
222+
223+
final @Nullable Long httpStatus = otelSpan.getAttribute(SemanticAttributes.HTTP_STATUS_CODE);
224+
if (httpStatus != null) {
225+
final @Nullable SpanStatus spanStatus = SpanStatus.fromHttpStatusCode(httpStatus.intValue());
226+
if (spanStatus != null) {
227+
return spanStatus;
228+
}
229+
}
230+
231+
return SpanStatus.UNKNOWN_ERROR;
232+
}
233+
234+
private boolean hasSentryBeenInitialized() {
235+
return Sentry.isEnabled();
236+
}
35237
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.sentry.opentelemetry;
2+
3+
import io.sentry.protocol.TransactionNameSource;
4+
import org.jetbrains.annotations.ApiStatus;
5+
import org.jetbrains.annotations.NotNull;
6+
7+
@ApiStatus.Internal
8+
public final class SpanDescription {
9+
10+
private final @NotNull String op;
11+
private final @NotNull String description;
12+
private final @NotNull TransactionNameSource transactionNameSource;
13+
14+
public SpanDescription(
15+
final @NotNull String op,
16+
final @NotNull String description,
17+
final @NotNull TransactionNameSource transactionNameSource) {
18+
this.op = op;
19+
this.description = description;
20+
this.transactionNameSource = transactionNameSource;
21+
}
22+
23+
public @NotNull String getOp() {
24+
return op;
25+
}
26+
27+
public @NotNull String getDescription() {
28+
return description;
29+
}
30+
31+
public @NotNull TransactionNameSource getTransactionNameSource() {
32+
return transactionNameSource;
33+
}
34+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.sentry.opentelemetry;
2+
3+
import io.opentelemetry.api.trace.SpanKind;
4+
import io.opentelemetry.sdk.trace.ReadableSpan;
5+
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
6+
import io.sentry.protocol.TransactionNameSource;
7+
import org.jetbrains.annotations.ApiStatus;
8+
import org.jetbrains.annotations.NotNull;
9+
import org.jetbrains.annotations.Nullable;
10+
11+
@ApiStatus.Internal
12+
public final class SpanDescriptionExtractor {
13+
14+
public @NotNull SpanDescription extractSpanDescription(final @NotNull ReadableSpan otelSpan) {
15+
final @NotNull String name = otelSpan.getName();
16+
17+
final @Nullable String httpMethod = otelSpan.getAttribute(SemanticAttributes.HTTP_METHOD);
18+
if (httpMethod != null) {
19+
return descriptionForHttpMethod(otelSpan, httpMethod);
20+
}
21+
22+
final @Nullable String dbSystem = otelSpan.getAttribute(SemanticAttributes.DB_SYSTEM);
23+
if (dbSystem != null) {
24+
return descriptionForDbSystem(otelSpan);
25+
}
26+
27+
return new SpanDescription(name, name, TransactionNameSource.CUSTOM);
28+
}
29+
30+
private SpanDescription descriptionForHttpMethod(
31+
final @NotNull ReadableSpan otelSpan, final @NotNull String httpMethod) {
32+
final @NotNull String name = otelSpan.getName();
33+
final @NotNull SpanKind kind = otelSpan.getKind();
34+
final @NotNull StringBuilder opBuilder = new StringBuilder("http");
35+
36+
if (SpanKind.CLIENT.equals(kind)) {
37+
opBuilder.append(".client");
38+
} else if (SpanKind.SERVER.equals(kind)) {
39+
opBuilder.append(".server");
40+
}
41+
final @Nullable String httpTarget = otelSpan.getAttribute(SemanticAttributes.HTTP_TARGET);
42+
final @Nullable String httpRoute = otelSpan.getAttribute(SemanticAttributes.HTTP_ROUTE);
43+
final @Nullable String httpPath = httpRoute != null ? httpRoute : httpTarget;
44+
final @NotNull String op = opBuilder.toString();
45+
46+
if (httpPath == null) {
47+
return new SpanDescription(op, name, TransactionNameSource.CUSTOM);
48+
}
49+
50+
final @NotNull String description = httpMethod + " " + httpPath;
51+
final @NotNull TransactionNameSource transactionNameSource =
52+
httpRoute != null ? TransactionNameSource.ROUTE : TransactionNameSource.URL;
53+
54+
return new SpanDescription(op, description, transactionNameSource);
55+
}
56+
57+
private SpanDescription descriptionForDbSystem(final @NotNull ReadableSpan otelSpan) {
58+
@Nullable String dbStatement = otelSpan.getAttribute(SemanticAttributes.DB_STATEMENT);
59+
@NotNull String description = dbStatement != null ? dbStatement : otelSpan.getName();
60+
return new SpanDescription("db", description, TransactionNameSource.TASK);
61+
}
62+
}

0 commit comments

Comments
 (0)