Skip to content

feat(quart): Add span streaming support to Quart integration#6502

Open
ericapisani wants to merge 17 commits into
masterfrom
py-2352-migrate-quart
Open

feat(quart): Add span streaming support to Quart integration#6502
ericapisani wants to merge 17 commits into
masterfrom
py-2352-migrate-quart

Conversation

@ericapisani

Copy link
Copy Markdown
Member

Add span streaming support for the Quart integration when the
trace_lifecycle stream experiment is enabled. Sets HTTP request
attributes (method, headers, URL, query, client IP) on the segment
span and uses the correct source constant from sentry_sdk.traces
for span-first mode.

Depends on #6501
being merged first.

Fixes PY-2352
Fixes #6050

Move http.query and client.address attribute collection inside the
should_send_default_pii() check so sensitive values are not captured
by default.

Fixes PY-2514
Fixes #6499
Add span streaming support for the Quart integration when the
trace_lifecycle stream experiment is enabled. Sets HTTP request
attributes (method, headers, URL, query, client IP) on the segment
span and uses the correct source constant from sentry_sdk.traces
for span-first mode.

Depends on #6501
being merged first.

Fixes PY-2352
Fixes #6050
@linear-code

linear-code Bot commented Jun 4, 2026

Copy link
Copy Markdown

PY-2352

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Codecov Results 📊

89523 passed | ⏭️ 6013 skipped | Total: 95536 | Pass Rate: 93.71% | Execution Time: 311m 35s

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +112
Passed Tests 📈 +112
Failed Tests
Skipped Tests

All tests are passing successfully.

✅ Patch coverage is 91.84%. Project has 2393 uncovered lines.
✅ Project coverage is 89.81%. Comparing base (base) to head (head).

Files with missing lines (1)
File Patch % Lines
sentry_sdk/integrations/quart.py 91.84% ⚠️ 4 Missing and 7 partials
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    89.80%    89.81%    +0.01%
==========================================
  Files          192       192         —
  Lines        23460     23495       +35
  Branches      8062      8084       +22
==========================================
+ Hits         21069     21102       +33
- Misses        2391      2393        +2
- Partials      1328      1332        +4

Generated by Codecov Action

@ericapisani ericapisani marked this pull request as ready for review June 4, 2026 14:11
@ericapisani ericapisani requested a review from a team as a code owner June 4, 2026 14:11
Comment thread sentry_sdk/integrations/quart.py Outdated
Comment thread sentry_sdk/integrations/quart.py
Comment thread sentry_sdk/integrations/quart.py Outdated
Comment on lines 123 to 125

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We'll need a streaming counterpart for this piece of code, something like:

client = sentry_sdk.get_client()

if has_span_streaming_enabled(client.options):
    span = get_current_span()
    if span is not None:
        span._segment._update_active_thread()

)
segment.set_attribute(
"user.ip_address", request_websocket.access_route[0]
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should also set user.id if we have it (essentially port _add_user_to_event).

It's one of the attributes that should be set on all spans, not just the segment, so it should be set on the scope.

Maybe extract the code that fetches the user id from _add_user_to_event to a helper func, get rid of _add_user_to_event and set the user id in the event processor directly, and in the streaming path set it on the current scope?

def _fetch_user_id() -> None:
    if quart_auth is None:
        return

    user = quart_auth.current_user
    if user is None:
        return

    try:
        return quart_auth.current_user._auth_id
    except Exception:
        return None

and then here

user_id = _fetch_user_id()
if user_id is not None:
    sentry_sdk.get_current_scope().set_attribute("user.id", user_id)

and in the event processor

user_id = _fetch_user_id()
if user_id is not None:
    user_info = event.setdefault("user", {})
    user_info["id"] = user_id

@sentrivana sentrivana Jun 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please also add a test for this if there is none or add to an existing one.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sounds good. Regarding needing to set the user.id on the scope - would that mean that we also need to make an adjustment for the user.ip_address as well? I noticed it also appears in that list that you had linked 👀

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's a really good point that made me realize we had a bug in the attribute setting code, fix here. Yup we should absolutely also be setting ip_address.

I need to amend my previous comment though: we should port setting the user but not the way I wrote. There's an existing mechanism for it (set_user) that we should use -- it sets the user data on the scope and then this code materializes it on the span eventually. The only thing to be mindful of is that set_user accepts a dict where the keys are different from the final attribute names (see the linked code), so it's e.g. name instead of user.name etc.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Another thing to note: set_user will completely overwrite whatever was there previously (it doesn't merge the dicts), so we need to collect all the fields we have and update once.

Comment thread sentry_sdk/integrations/quart.py
SENTRY_PYTHON_TEST_POSTGRES_USER: postgres
SENTRY_PYTHON_TEST_POSTGRES_PASSWORD: sentry
SENTRY_PYTHON_TEST_MYSQL_USER: root
SENTRY_PYTHON_TEST_MYSQL_PASSWORD: sentry

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing MySQL host env variable

Medium Severity

The DB CI job sets SENTRY_PYTHON_TEST_POSTGRES_HOST to postgres for Python 3.6/3.7 container runs but does not set SENTRY_PYTHON_TEST_MYSQL_HOST the same way for the new MySQL service. tests/integrations/aiomysql reads that variable and defaults to localhost, which is unreachable from the job container network.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dde12e6. Configure here.

Comment thread sentry_sdk/integrations/starlette.py
SENTRY_PYTHON_TEST_POSTGRES_USER: postgres
SENTRY_PYTHON_TEST_POSTGRES_PASSWORD: sentry
SENTRY_PYTHON_TEST_MYSQL_USER: root
SENTRY_PYTHON_TEST_MYSQL_PASSWORD: sentry

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing MySQL host container env

Medium Severity

The new MySQL CI env sets SENTRY_PYTHON_TEST_MYSQL_USER and password but not SENTRY_PYTHON_TEST_MYSQL_HOST. For Python 3.6/3.7 jobs that run inside a service-linked container, tests/integrations/aiomysql defaults to localhost, unlike Postgres and Redis which use the mysql service hostname in that layout.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e5eb0b3. Configure here.

Base automatically changed from py-2514-gate-asgi-values-behind-pii to master June 11, 2026 14:51
Comment thread sentry_sdk/integrations/quart.py Outdated
Comment thread tests/integrations/quart/test_quart.py

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 717731d. Configure here.

segment = spans[1]
if send_default_pii and user_id is not None:
assert segment["attributes"]["user.id"] == user_id
print("SEGMENT ATTRS", segment["attributes"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Debug print left in test

Low Severity

A print("SEGMENT ATTRS", ...) call remains in test_span_streaming_quart_auth_user_id, which will emit noisy output whenever that test runs under PII with a logged-in user.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 717731d. Configure here.

user_properties["id"] = current_user_id

if user_properties:
sentry_sdk.get_current_scope().set_user(user_properties)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

set_user drops existing scope user

Medium Severity

The span-streaming path calls set_user with only newly built user_properties (IP and Quart auth id) on the current scope, so any user fields already on the isolation scope are replaced instead of merged before one update.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 717731d. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate quart to span first

2 participants