Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
129 changes: 80 additions & 49 deletions sentry_sdk/integrations/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
TransactionSource,
)
from sentry_sdk.tracing_utils import (
add_http_breadcrumb,
add_http_request_source,
has_span_streaming_enabled,
should_propagate_trace,
Expand Down Expand Up @@ -388,57 +389,64 @@
with capture_internal_exceptions():
parsed_url = parse_url(str(params.url), sanitize=False)

breadcrumb = {}

span_name = "%s %s" % (
method,
parsed_url.url if parsed_url else SENSITIVE_DATA_SUBSTITUTE,
)

span: "Union[Span, StreamedSpan, None]"
span: "Union[Span, StreamedSpan, None]" = None
if has_span_streaming_enabled(client.options):
if sentry_sdk.traces.get_current_span() is None:
span = None

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This had to be moved after the PII redaction, because even if we don't want to create a span, we do want to create a breadcrumb, and we need to apply the same PII redacting logic to breadcrumbs.

else:
attributes: "Attributes" = {
"sentry.op": OP.HTTP_CLIENT,
"sentry.origin": AioHttpIntegration.origin,
"http.request.method": method,
}
if parsed_url is not None:
if has_data_collection_enabled(client.options):
url_full = parsed_url.url
attributes["url.path"] = params.url.path

if parsed_url.query:
filtered_query = (
_apply_data_collection_filtering_to_query_string(
query_string=parsed_url.query,
behaviour=client.options["data_collection"][
"url_query_params"
],
)
attributes: "Attributes" = {
"sentry.op": OP.HTTP_CLIENT,
"sentry.origin": AioHttpIntegration.origin,
"http.request.method": method,
}
if parsed_url is not None:
if has_data_collection_enabled(client.options):
url_full = parsed_url.url
attributes["url.path"] = params.url.path

if parsed_url.query:
filtered_query = (
_apply_data_collection_filtering_to_query_string(
query_string=parsed_url.query,
behaviour=client.options["data_collection"][
"url_query_params"
],
)
if filtered_query:
attributes["url.query"] = filtered_query
url_full += "?" + filtered_query

if parsed_url.fragment:
attributes["url.fragment"] = parsed_url.fragment
url_full += "#" + parsed_url.fragment

attributes["url.full"] = url_full
elif should_send_default_pii():
url_full = parsed_url.url
attributes["url.path"] = params.url.path

if parsed_url.query:
url_full += "?" + parsed_url.query
attributes["url.query"] = parsed_url.query
if parsed_url.fragment:
url_full += "#" + parsed_url.fragment
attributes["url.fragment"] = parsed_url.fragment

attributes["url.full"] = url_full

)
if filtered_query:
attributes["url.query"] = filtered_query
url_full += "?" + filtered_query
breadcrumb[SPANDATA.HTTP_QUERY] = filtered_query

if parsed_url.fragment:
attributes["url.fragment"] = parsed_url.fragment
url_full += "#" + parsed_url.fragment
breadcrumb[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment

attributes["url.full"] = url_full
breadcrumb["url"] = url_full

elif should_send_default_pii():
url_full = parsed_url.url
attributes["url.path"] = params.url.path

if parsed_url.query:
url_full += "?" + parsed_url.query
attributes["url.query"] = parsed_url.query
breadcrumb[SPANDATA.HTTP_QUERY] = parsed_url.query
if parsed_url.fragment:
url_full += "#" + parsed_url.fragment
attributes["url.fragment"] = parsed_url.fragment
breadcrumb[SPANDATA.HTTP_FRAGMENT] = parsed_url.fragment

attributes["url.full"] = url_full
breadcrumb["url"] = url_full

if sentry_sdk.traces.get_current_span() is not None:
span = sentry_sdk.traces.start_span(
name=span_name, attributes=attributes
)
Expand All @@ -450,9 +458,16 @@
)
legacy_span.set_data(SPANDATA.HTTP_METHOD, method)
if parsed_url is not None:
legacy_span.set_data("url", parsed_url.url)
legacy_span.set_data(SPANDATA.HTTP_QUERY, parsed_url.query)
legacy_span.set_data(SPANDATA.HTTP_FRAGMENT, parsed_url.fragment)
breadcrumb.update(
{
SPANDATA.HTTP_QUERY: parsed_url.query,
SPANDATA.HTTP_FRAGMENT: parsed_url.fragment,
"url": parsed_url.url,
}

Check warning on line 469 in sentry_sdk/integrations/aiohttp.py

View check run for this annotation

@sentry/warden / warden: find-bugs

Legacy span path leaks unfiltered URL data into breadcrumbs

In the legacy span `else:` block, raw `parsed_url.query` and `parsed_url.fragment` are added to the breadcrumb unconditionally, bypassing `should_send_default_pii`/`data_collection` checks that the streaming path correctly applies.
Comment thread
sentrivana marked this conversation as resolved.
)
span = legacy_span

if should_propagate_trace(client, str(params.url)):
Expand All @@ -475,19 +490,35 @@
else:
params.headers[key] = value

trace_config_ctx.span = span
trace_config_ctx._sentry_span = span
trace_config_ctx._sentry_breadcrumb = breadcrumb

async def on_request_end(
session: "ClientSession",
trace_config_ctx: "SimpleNamespace",
params: "TraceRequestEndParams",
) -> None:
if trace_config_ctx.span is None:
return

span = trace_config_ctx.span
status = int(params.response.status)

breadcrumb = getattr(trace_config_ctx, "_sentry_breadcrumb", None)
if breadcrumb is not None:
breadcrumb.update(
{
SPANDATA.HTTP_METHOD: params.method.upper(),
SPANDATA.HTTP_STATUS_CODE: status,
"reason": params.response.reason,
}
)

add_http_breadcrumb(
status,
breadcrumb,
)

span = getattr(trace_config_ctx, "_sentry_span", None)
if span is None:
return

if isinstance(span, StreamedSpan):
span.set_attribute("http.response.status_code", status)
span.status = (
Expand Down
17 changes: 16 additions & 1 deletion sentry_sdk/tracing_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,10 +210,25 @@ def record_sql_queries(
yield span


def add_http_breadcrumb(status_code: "Optional[int]", data: "dict[str, Any]") -> None:
level = None
if status_code:
if 500 <= status_code <= 599:
level = "error"
elif 400 <= status_code <= 499:
level = "warning"

kwargs: "dict[str, Any]" = {"type": "http", "category": "httplib", "data": data}
if level:
kwargs["level"] = level

sentry_sdk.add_breadcrumb(**kwargs)


def maybe_create_breadcrumbs_from_span(
scope: "sentry_sdk.Scope", span: "sentry_sdk.tracing.Span"
) -> None:
if span.op == OP.HTTP_CLIENT:
if span.op == OP.HTTP_CLIENT and span.origin not in ("auto.http.aiohttp",):

@sentrivana sentrivana Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just here to make sure we're not creating breadcrumbs the old way in transaction-based tracing anymore. Once all HTTP client integrations have been migrated, the whole function will go away

level = None
status_code = span._data.get(SPANDATA.HTTP_STATUS_CODE)
if status_code:
Expand Down
Loading
Loading