1
- from opentelemetry .trace import INVALID_SPAN , get_current_span # type: ignore
2
- from opentelemetry .context import Context # type: ignore
3
- from opentelemetry .sdk .trace import Span , ReadableSpan , SpanProcessor # type: ignore
1
+ from collections import deque
2
+ from datetime import datetime
4
3
5
- from sentry_sdk .integrations .opentelemetry .utils import is_sentry_span
6
- from sentry_sdk .integrations .opentelemetry .potel_span_exporter import (
7
- PotelSentrySpanExporter ,
8
- )
4
+ from opentelemetry .trace import INVALID_SPAN , get_current_span , format_trace_id , format_span_id
5
+ from opentelemetry .context import Context
6
+ from opentelemetry .sdk .trace import Span , ReadableSpan , SpanProcessor
9
7
8
+ from sentry_sdk import capture_event
9
+ from sentry_sdk .integrations .opentelemetry .utils import is_sentry_span , convert_otel_timestamp
10
+ from sentry_sdk .integrations .opentelemetry .consts import OTEL_SENTRY_CONTEXT , SPAN_ORIGIN
10
11
from sentry_sdk ._types import TYPE_CHECKING
11
12
12
13
if TYPE_CHECKING :
13
- from typing import Optional
14
+ from typing import Optional , List , Any
15
+ from sentry_sdk ._types import Event
14
16
15
17
16
18
class PotelSentrySpanProcessor (SpanProcessor ): # type: ignore
@@ -27,25 +29,23 @@ def __new__(cls):
27
29
28
30
def __init__ (self ):
29
31
# type: () -> None
30
- self ._exporter = PotelSentrySpanExporter ()
32
+ self ._children_spans = {} # type: dict[int, List[ReadableSpan]]
31
33
32
34
def on_start (self , span , parent_context = None ):
33
35
# type: (Span, Optional[Context]) -> None
34
36
pass
35
- # if is_sentry_span(span):
36
- # return
37
-
38
- # parent_span = get_current_span(parent_context)
39
-
40
- # # TODO-neel-potel check remote logic with propagation and incoming trace later
41
- # if parent_span != INVALID_SPAN:
42
- # # TODO-neel once we add our apis, we might need to store references on the span
43
- # # directly, see if we need to do this like JS
44
- # pass
45
37
46
38
def on_end (self , span ):
47
39
# type: (ReadableSpan) -> None
48
- self ._exporter .export (span )
40
+ if is_sentry_span (span ):
41
+ return
42
+
43
+ # TODO-neel-potel-remote only take parent if not remote
44
+ if span .parent :
45
+ self ._children_spans .setdefault (span .parent .span_id , []).append (span )
46
+ else :
47
+ # if have a root span ending, we build a transaction and send it
48
+ self ._flush_root_span (span )
49
49
50
50
# TODO-neel-potel not sure we need a clear like JS
51
51
def shutdown (self ):
@@ -56,4 +56,77 @@ def shutdown(self):
56
56
# TODO-neel-potel call this in client.flush
57
57
def force_flush (self , timeout_millis = 30000 ):
58
58
# type: (int) -> bool
59
- return self ._exporter .flush (timeout_millis )
59
+ return True
60
+
61
+ def _flush_root_span (self , span ):
62
+ # type: (ReadableSpan) -> None
63
+ transaction_event = self ._root_span_to_transaction_event (span )
64
+ if not transaction_event :
65
+ return
66
+
67
+ children = self ._collect_children (span )
68
+ # TODO add converted spans
69
+ capture_event (transaction_event )
70
+
71
+
72
+ def _collect_children (self , span ):
73
+ # type: (ReadableSpan) -> List[ReadableSpan]
74
+ if not span .context :
75
+ return []
76
+
77
+ children = []
78
+ bfs_queue = deque ()
79
+ bfs_queue .append (span .context .span_id )
80
+
81
+ while bfs_queue :
82
+ parent_span_id = bfs_queue .popleft ()
83
+ node_children = self ._children_spans .pop (parent_span_id , [])
84
+ children .extend (node_children )
85
+ bfs_queue .extend ([child .context .span_id for child in node_children if child .context ])
86
+
87
+ return children
88
+
89
+ # we construct the event from scratch here
90
+ # and not use the current Transaction class for easier refactoring
91
+ # TODO-neel-potel op, description, status logic
92
+ def _root_span_to_transaction_event (self , span ):
93
+ # type: (ReadableSpan) -> Optional[Event]
94
+ if not span .context :
95
+ return None
96
+ if not span .start_time :
97
+ return None
98
+ if not span .end_time :
99
+ return None
100
+
101
+ trace_id = format_trace_id (span .context .trace_id )
102
+ span_id = format_span_id (span .context .span_id )
103
+ parent_span_id = format_span_id (span .parent .span_id ) if span .parent else None
104
+
105
+ trace_context = {
106
+ "trace_id" : trace_id ,
107
+ "span_id" : span_id ,
108
+ "origin" : SPAN_ORIGIN ,
109
+ "op" : span .name , # TODO
110
+ "status" : "ok" , # TODO
111
+ } # type: dict[str, Any]
112
+
113
+ if parent_span_id :
114
+ trace_context ["parent_span_id" ] = parent_span_id
115
+ if span .attributes :
116
+ trace_context ["data" ] = dict (span .attributes )
117
+
118
+ contexts = {"trace" : trace_context }
119
+ if span .resource .attributes :
120
+ contexts [OTEL_SENTRY_CONTEXT ] = {"resource" : dict (span .resource .attributes )}
121
+
122
+ event = {
123
+ "type" : "transaction" ,
124
+ "transaction" : span .name , # TODO
125
+ "transaction_info" : {"source" : "custom" }, # TODO
126
+ "contexts" : contexts ,
127
+ "start_timestamp" : convert_otel_timestamp (span .start_time ),
128
+ "timestamp" : convert_otel_timestamp (span .end_time ),
129
+ "spans" : [],
130
+ } # type: Event
131
+
132
+ return event
0 commit comments