Skip to content

Commit d843efc

Browse files
authored
feat(strawberry): Support span streaming (#6308)
Add span streaming support to the Strawberry GraphQL integration, mirroring the pattern used in the starlite and aiohttp integrations. Fixes PY-2366 Fixes #6063
1 parent 0af4a8b commit d843efc

2 files changed

Lines changed: 668 additions & 271 deletions

File tree

sentry_sdk/integrations/strawberry.py

Lines changed: 116 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version
99
from sentry_sdk.integrations.logging import ignore_logger
1010
from sentry_sdk.scope import should_send_default_pii
11-
from sentry_sdk.tracing import TransactionSource
11+
from sentry_sdk.traces import SegmentSource
12+
from sentry_sdk.tracing import Span, TransactionSource
13+
from sentry_sdk.tracing_utils import StreamedSpan, has_span_streaming_enabled
1214
from sentry_sdk.utils import (
1315
capture_internal_exceptions,
1416
ensure_integration_enabled,
@@ -183,50 +185,111 @@ def on_operation(self) -> "Generator[None, None, None]":
183185
event_processor = _make_request_event_processor(self.execution_context)
184186
scope.add_event_processor(event_processor)
185187

186-
graphql_span = sentry_sdk.start_span(
187-
op=op,
188-
name=description,
189-
origin=StrawberryIntegration.origin,
190-
)
191-
graphql_span.__enter__()
188+
client = sentry_sdk.get_client()
189+
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
190+
if is_span_streaming_enabled:
191+
additional_attributes: "dict[str, Any]" = {}
192+
193+
if should_send_default_pii():
194+
additional_attributes["graphql.document"] = self.execution_context.query
195+
196+
if operation_name:
197+
additional_attributes["graphql.operation.name"] = operation_name
198+
199+
graphql_span = sentry_sdk.traces.start_span(
200+
name=description,
201+
attributes={
202+
"sentry.origin": StrawberryIntegration.origin,
203+
"sentry.op": op,
204+
"graphql.operation.type": operation_type,
205+
**additional_attributes,
206+
},
207+
)
208+
else:
209+
graphql_span = sentry_sdk.start_span(
210+
op=op,
211+
name=description,
212+
origin=StrawberryIntegration.origin,
213+
)
214+
graphql_span.__enter__()
215+
216+
if type(graphql_span) is Span:
217+
if should_send_default_pii():
218+
graphql_span.set_data("graphql.document", self.execution_context.query)
192219

193-
graphql_span.set_data("graphql.operation.type", operation_type)
194-
graphql_span.set_data("graphql.operation.name", operation_name)
195-
if should_send_default_pii():
196-
graphql_span.set_data("graphql.document", self.execution_context.query)
197-
graphql_span.set_data("graphql.resource_name", self._resource_name)
220+
graphql_span.set_data("graphql.operation.type", operation_type)
221+
graphql_span.set_data("graphql.operation.name", operation_name)
222+
# This attribute is being removed in streamed spans
223+
graphql_span.set_data("graphql.resource_name", self._resource_name)
198224

199225
yield
200226

201-
transaction = graphql_span.containing_transaction
202-
if transaction and self.execution_context.operation_name:
203-
transaction.name = self.execution_context.operation_name
204-
transaction.source = TransactionSource.COMPONENT
205-
transaction.op = op
227+
if type(graphql_span) is StreamedSpan:
228+
if self.execution_context.operation_name:
229+
segment = graphql_span._segment
230+
segment.set_attribute("sentry.span.source", SegmentSource.COMPONENT)
231+
segment.set_attribute("sentry.op", op)
232+
segment.name = self.execution_context.operation_name
233+
elif isinstance(graphql_span, Span):
234+
transaction = graphql_span.containing_transaction
235+
if transaction and self.execution_context.operation_name:
236+
transaction.name = self.execution_context.operation_name
237+
transaction.source = TransactionSource.COMPONENT
238+
transaction.op = op
206239

207240
graphql_span.__exit__(None, None, None)
208241

209242
def on_validate(self) -> "Generator[None, None, None]":
210-
validation_span = sentry_sdk.start_span(
211-
op=OP.GRAPHQL_VALIDATE,
212-
name="validation",
213-
origin=StrawberryIntegration.origin,
214-
)
243+
client = sentry_sdk.get_client()
244+
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
245+
246+
if is_span_streaming_enabled:
247+
validation_span = sentry_sdk.traces.start_span(
248+
name="validation",
249+
attributes={
250+
"sentry.op": OP.GRAPHQL_VALIDATE,
251+
"sentry.origin": StrawberryIntegration.origin,
252+
},
253+
)
254+
else:
255+
validation_span = sentry_sdk.start_span(
256+
op=OP.GRAPHQL_VALIDATE,
257+
name="validation",
258+
origin=StrawberryIntegration.origin,
259+
)
215260

216261
yield
217262

218-
validation_span.finish()
263+
if isinstance(validation_span, StreamedSpan):
264+
validation_span.end()
265+
else:
266+
validation_span.finish()
219267

220268
def on_parse(self) -> "Generator[None, None, None]":
221-
parsing_span = sentry_sdk.start_span(
222-
op=OP.GRAPHQL_PARSE,
223-
name="parsing",
224-
origin=StrawberryIntegration.origin,
225-
)
269+
client = sentry_sdk.get_client()
270+
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
271+
272+
if is_span_streaming_enabled:
273+
parsing_span = sentry_sdk.traces.start_span(
274+
name="parsing",
275+
attributes={
276+
"sentry.op": OP.GRAPHQL_PARSE,
277+
"sentry.origin": StrawberryIntegration.origin,
278+
},
279+
)
280+
else:
281+
parsing_span = sentry_sdk.start_span(
282+
op=OP.GRAPHQL_PARSE,
283+
name="parsing",
284+
origin=StrawberryIntegration.origin,
285+
)
226286

227287
yield
228288

229-
parsing_span.finish()
289+
if isinstance(parsing_span, StreamedSpan):
290+
parsing_span.end()
291+
else:
292+
parsing_span.finish()
230293

231294
def should_skip_tracing(
232295
self,
@@ -263,6 +326,18 @@ async def resolve(
263326

264327
field_path = "{}.{}".format(info.parent_type, info.field_name)
265328

329+
client = sentry_sdk.get_client()
330+
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
331+
if is_span_streaming_enabled:
332+
with sentry_sdk.traces.start_span(
333+
name=f"resolving {field_path}",
334+
attributes={
335+
"sentry.origin": StrawberryIntegration.origin,
336+
"sentry.op": OP.GRAPHQL_RESOLVE,
337+
},
338+
):
339+
return await self._resolve(_next, root, info, *args, **kwargs)
340+
266341
with sentry_sdk.start_span(
267342
op=OP.GRAPHQL_RESOLVE,
268343
name="resolving {}".format(field_path),
@@ -290,6 +365,18 @@ def resolve(
290365

291366
field_path = "{}.{}".format(info.parent_type, info.field_name)
292367

368+
client = sentry_sdk.get_client()
369+
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
370+
if is_span_streaming_enabled:
371+
with sentry_sdk.traces.start_span(
372+
name=f"resolving {field_path}",
373+
attributes={
374+
"sentry.origin": StrawberryIntegration.origin,
375+
"sentry.op": OP.GRAPHQL_RESOLVE,
376+
},
377+
):
378+
return _next(root, info, *args, **kwargs)
379+
293380
with sentry_sdk.start_span(
294381
op=OP.GRAPHQL_RESOLVE,
295382
name="resolving {}".format(field_path),

0 commit comments

Comments
 (0)