Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions sentry_sdk/integrations/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,23 +351,28 @@ async def on_request_start(
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
)

span: "Union[Span, StreamedSpan]"
span: "Union[Span, StreamedSpan, None]"
if has_span_streaming_enabled(client.options):
attributes: "Attributes" = {
"sentry.op": OP.HTTP_CLIENT,
"sentry.origin": AioHttpIntegration.origin,
"http.request.method": method,
}
if parsed_url is not None and should_send_default_pii():
attributes["url.full"] = parsed_url.url
attributes["url.path"] = params.url.path

if parsed_url.query:
attributes["url.query"] = parsed_url.query
if parsed_url.fragment:
attributes["url.fragment"] = parsed_url.fragment

span = sentry_sdk.traces.start_span(name=span_name, attributes=attributes)
if sentry_sdk.traces.get_current_span() is None:
span = None
else:
attributes: "Attributes" = {
"sentry.op": OP.HTTP_CLIENT,
"sentry.origin": AioHttpIntegration.origin,
"http.request.method": method,
}
if parsed_url is not None and should_send_default_pii():
attributes["url.full"] = parsed_url.url
attributes["url.path"] = params.url.path

if parsed_url.query:
attributes["url.query"] = parsed_url.query
if parsed_url.fragment:
attributes["url.fragment"] = parsed_url.fragment

span = sentry_sdk.traces.start_span(
name=span_name, attributes=attributes
)
else:
legacy_span = sentry_sdk.start_span(
op=OP.HTTP_CLIENT,
Expand Down
4 changes: 4 additions & 0 deletions sentry_sdk/integrations/graphene.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ def graphql_span(
)

if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
yield
return

additional_attributes = {}
if should_send_default_pii():
additional_attributes["graphql.document"] = source
Expand Down
6 changes: 6 additions & 0 deletions sentry_sdk/integrations/litestar.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ async def _create_span_call(

middleware_name = self.__class__.__name__
if has_span_streaming_enabled(client.options):
if sentry_sdk.traces.get_current_span() is None:
return await old_call(self, scope, receive, send)
with sentry_sdk.traces.start_span(
name=middleware_name,
attributes={
Expand All @@ -179,6 +181,8 @@ async def _sentry_receive(
) -> "Union[HTTPReceiveMessage, WebSocketReceiveMessage]":
if client.get_integration(LitestarIntegration) is None:
return await receive(*args, **kwargs)
if sentry_sdk.traces.get_current_span() is None:
return await receive(*args, **kwargs)
with sentry_sdk.traces.start_span(
name=getattr(receive, "__qualname__", str(receive)),
attributes={
Expand All @@ -197,6 +201,8 @@ async def _sentry_receive(
async def _sentry_send(message: "Message") -> None:
if client.get_integration(LitestarIntegration) is None:
return await send(message)
if sentry_sdk.traces.get_current_span() is None:
return await send(message)
with sentry_sdk.traces.start_span(
name=getattr(send, "__qualname__", str(send)),
attributes={
Expand Down
3 changes: 3 additions & 0 deletions sentry_sdk/integrations/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
capture_internal_exceptions,
ensure_integration_enabled,
event_from_exception,
nullcontext,
parse_version,
transaction_from_function,
)
Expand Down Expand Up @@ -198,6 +199,8 @@ async def _create_span_call(

def _start_middleware_span(op: str, name: str) -> "Any":
if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
return nullcontext()
return sentry_sdk.traces.start_span(
name=name,
attributes={
Expand Down
3 changes: 3 additions & 0 deletions sentry_sdk/integrations/starlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from sentry_sdk.utils import (
ensure_integration_enabled,
event_from_exception,
nullcontext,
transaction_from_function,
)

Expand Down Expand Up @@ -151,6 +152,8 @@ async def _create_span_call(

def _start_middleware_span(op: str, name: str) -> "Any":
if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
return nullcontext()
return sentry_sdk.traces.start_span(
name=name,
attributes={
Expand Down
18 changes: 18 additions & 0 deletions sentry_sdk/integrations/strawberry.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ def on_operation(self) -> "Generator[None, None, None]":
client = sentry_sdk.get_client()
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
yield
return

additional_attributes: "dict[str, Any]" = {}

if should_send_default_pii():
Expand Down Expand Up @@ -244,6 +248,10 @@ def on_validate(self) -> "Generator[None, None, None]":
is_span_streaming_enabled = has_span_streaming_enabled(client.options)

if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
yield
return

validation_span = sentry_sdk.traces.start_span(
name="validation",
attributes={
Expand Down Expand Up @@ -272,6 +280,10 @@ def on_parse(self) -> "Generator[None, None, None]":
is_span_streaming_enabled = has_span_streaming_enabled(client.options)

if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
yield
return

parsing_span = sentry_sdk.traces.start_span(
name="parsing",
attributes={
Expand Down Expand Up @@ -333,6 +345,9 @@ async def resolve(
client = sentry_sdk.get_client()
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
return await self._resolve(_next, root, info, *args, **kwargs)

with sentry_sdk.traces.start_span(
name=f"resolving {field_path}",
attributes={
Expand Down Expand Up @@ -372,6 +387,9 @@ def resolve(
client = sentry_sdk.get_client()
is_span_streaming_enabled = has_span_streaming_enabled(client.options)
if is_span_streaming_enabled:
if sentry_sdk.traces.get_current_span() is None:
return _next(root, info, *args, **kwargs)

with sentry_sdk.traces.start_span(
name=f"resolving {field_path}",
attributes={
Expand Down
91 changes: 29 additions & 62 deletions tests/integrations/aiohttp/test_aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1162,16 +1162,12 @@ async def hello(request):

sentry_sdk.flush()

# The aiohttp_client fixture is itself sentry-instrumented and emits the
# first http.client segment; the server-side http.server span is the other
# segment. Asserting the exact length confirms no other spans leak in.
assert len(items) == 2

server_span, client_span = [item.payload for item in items]
# The server-side http.server span is the only segment. The aiohttp_client
# fixture's outgoing http.client span is suppressed because there is no
# active span when the test client makes the request.
assert len(items) == 1

assert client_span["is_segment"] is True
assert client_span["attributes"]["sentry.op"] == "http.client"
assert client_span["name"].startswith("GET http://127.0.0.1:")
(server_span,) = [item.payload for item in items]

assert server_span["is_segment"] is True
assert (
Expand Down Expand Up @@ -1242,7 +1238,7 @@ async def hello(request):

sentry_sdk.flush()

server_span, _client_segment = [item.payload for item in items]
(server_span,) = [item.payload for item in items]

# send_default_pii defaults to False, so _filter_headers substitutes
# sensitive headers with SENSITIVE_DATA_SUBSTITUTE ("[Filtered]"). The
Expand Down Expand Up @@ -1282,7 +1278,7 @@ async def hello(request):

sentry_sdk.flush()

server_span, _client_segment = [item.payload for item in items]
(server_span,) = [item.payload for item in items]

# With send_default_pii=True, _filter_headers is a no-op and the original
# value reaches the span attribute.
Expand Down Expand Up @@ -1321,8 +1317,8 @@ async def hello(request):

sentry_sdk.flush()

assert len(items) == 2
server_segment, client_segment = [item.payload for item in items]
assert len(items) == 1
(server_segment,) = [item.payload for item in items]

if send_pii:
assert server_segment["attributes"]["url.query"] == "foo=bar&baz=qux"
Expand Down Expand Up @@ -1378,8 +1374,8 @@ async def hello(request):

sentry_sdk.flush()

assert len(items) == 2
server_segment, client_segment = [item.payload for item in items]
assert len(items) == 1
(server_segment,) = [item.payload for item in items]

assert server_segment["name"] == expected_name
assert server_segment["is_segment"]
Expand Down Expand Up @@ -1408,21 +1404,14 @@ async def hello(request):

sentry_sdk.flush()

# 1 error event + 2 spans (server http.server, test client http.client segment)
assert len(items) == 3
# 1 error event + 1 span (server http.server)
assert len(items) == 2

error_event = items[0]
assert error_event.type == "event"
assert error_event.payload["exception"]["values"][0]["type"] == "ZeroDivisionError"

server_span, segment = [item.payload for item in items[1:]]

assert segment["is_segment"] is True
assert segment["attributes"]["sentry.op"] == "http.client"
# The test client receives the 500 response that aiohttp's outer error
# handler synthesizes from the unhandled exception.
assert segment["attributes"]["http.response.status_code"] == 500
assert segment["status"] == "error"
server_span = items[1].payload

# The integration's generic Exception path reraises without recording
# http.response.status_code on the server span. StreamedSpan.__exit__
Expand Down Expand Up @@ -1456,13 +1445,8 @@ async def hello(request):

sentry_sdk.flush()

assert len(items) == 2
server_span, segment = [item.payload for item in items]

assert segment["is_segment"] is True
assert segment["attributes"]["sentry.op"] == "http.client"
assert segment["attributes"]["http.response.status_code"] == 403
assert segment["status"] == "error"
assert len(items) == 1
(server_span,) = [item.payload for item in items]

assert server_span["attributes"]["sentry.op"] == "http.server"
assert server_span["attributes"]["http.response.status_code"] == 403
Expand Down Expand Up @@ -1493,13 +1477,8 @@ async def hello(request):

sentry_sdk.flush()

assert len(items) == 2
server_span, segment = [item.payload for item in items]

assert segment["is_segment"] is True
assert segment["attributes"]["sentry.op"] == "http.client"
assert segment["attributes"]["http.response.status_code"] == 302
assert segment["status"] == "ok"
assert len(items) == 1
(server_span,) = [item.payload for item in items]

assert server_span["attributes"]["sentry.op"] == "http.server"
assert server_span["attributes"]["http.response.status_code"] == 302
Expand Down Expand Up @@ -1538,18 +1517,13 @@ async def hello(request):

sentry_sdk.flush()

# 3 spans, finished inner-first:
# 2 spans, finished inner-first:
# #0 inner http.client (server -> raw_server)
# #1 server http.server
# #2 outer http.client segment (test client -> server)
assert len(items) == 3
assert len(items) == 2

inner_client_span = items[0].payload
server_span = items[1].payload
segment = items[2].payload

assert segment["is_segment"] is True
assert segment["attributes"]["sentry.op"] == "http.client"

assert server_span["attributes"]["sentry.op"] == "http.server"

Expand Down Expand Up @@ -1596,21 +1570,14 @@ async def handler(request):

sentry_sdk.flush()

# raw_server bypasses Application._handle, so only the test client's
# outgoing http.client segment is emitted.
assert len(items) == 1
client_span = items[0].payload

assert client_span["is_segment"] is True
assert client_span["attributes"]["sentry.op"] == "http.client"
assert client_span["name"].startswith("GET http://127.0.0.1:")
assert resp.request_info.headers[
"sentry-trace"
] == "{trace_id}-{span_id}-{sampled}".format(
trace_id=client_span["trace_id"],
span_id=client_span["span_id"],
sampled=1,
)
# The outgoing http.client span is suppressed because there is no active
# span when the test client makes the request.
assert len(items) == 0

# Even though no span is created, the trace propagation headers must still
# be added to the outgoing request so the trace is not broken.
assert "sentry-trace" in resp.request_info.headers
assert "baggage" in resp.request_info.headers


@pytest.mark.asyncio
Comment thread
sentry-warden[bot] marked this conversation as resolved.
Expand Down Expand Up @@ -1640,7 +1607,7 @@ async def hello(request):

sentry_sdk.flush()

child_span, server_span, client_span = [item.payload for item in items]
child_span, server_span = [item.payload for item in items]

if send_default_pii:
assert server_span["attributes"]["user.ip_address"] == "127.0.0.1"
Expand Down
Loading