11package 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 ;
36import io .opentelemetry .context .Context ;
47import io .opentelemetry .sdk .trace .ReadWriteSpan ;
58import io .opentelemetry .sdk .trace .ReadableSpan ;
69import 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 ;
718import 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" )
1034public 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}
0 commit comments