getsentry/sentry-python

Flask integration does not create transactions when called via Flask.wsgi_app (like Connexion 3.x)

Opened this issue · 8 comments

How do you use Sentry?

Sentry Saas (sentry.io)

Version

2.34.1

Steps to Reproduce

  1. pip install 'connexion[flask]==3.*' sentry_sdk gunicorn uvicorn

  2. Create app.py and openapi.yaml as below

    openapi.yaml
    openapi: 3.0.0
    info:
      title: Test
      version: 1.0.0
    paths:
      /api:
        get:
          operationId: app.test_op
          responses:
            '200':
              description: Test response
              content:
                application/json:
                  schema:
                    type: string
    app.py
    import connexion
    import sentry_sdk
    
    def debug(evt, hint):
        print(evt["contexts"]["trace"]["trace_id"])
        return evt
    
    sentry_sdk.init(debug=True, before_send=debug, traces_sample_rate=1.0)
    
    def test_op():
        return "Hello world"
    
    if __name__ == "__main__":
        app = connexion.FlaskApp("app")
        app.add_api("openapi.yaml")
        app.run(port=5000)
  3. python app.py

  4. Access http://localhost:5000/api a few times

pip freeze --all
a2wsgi==1.10.10
anyio==4.10.0
asgiref==3.9.1
attrs==25.3.0
blinker==1.9.0
certifi==2025.8.3
charset-normalizer==3.4.3
click==8.2.1
connexion==3.2.0
distlib==0.3.9
filelock==3.18.0
Flask==3.1.1
gunicorn==23.0.0
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.10
inflection==0.5.1
itsdangerous==2.2.0
Jinja2==3.1.6
jsonschema==4.25.0
jsonschema-specifications==2025.4.1
MarkupSafe==3.0.2
packaging==25.0
pip==25.0.1
platformdirs==4.3.7
python-multipart==0.0.20
PyYAML==6.0.2
referencing==0.36.2
requests==2.32.4
rpds-py==0.27.0
sentry-sdk==2.34.1
sniffio==1.3.1
starlette==0.47.2
typing_extensions==4.14.1
urllib3==2.5.0
uvicorn==0.35.0
virtualenv==20.29.3
Werkzeug==3.1.3

Expected Result

Trace ID changes for each request, as transactions are created

Actual Result

Each request prints the same trace_id (and all events end up in the same transaction)

Why this occurs:

We use Connexion's FlaskApp as our current API middleware. In Connexion v2, this is implemented as an ASGI wrapper that directly calls Flask.wsgi_app. This is also the official wrapping point recommended by Flask.

https://github.com/spec-first/connexion/blob/1844a2fb3f1d09a37edfeab7a7200d52e97abbd9/connexion/apps/flask.py#L129-L141

The Sentry Flask integration only hooks Flask.__call__ and therefore doesn't receive transactions from the wrapped Flask app.

We could probably change to using the ASGI integration, but routing happens within Flask, so that doesn't have proper transaction metadata when the transaction is created. Therefore, it might make sense to hook wsgi_app instead. (As a mitigation we'll probably internally monkey-patch wsgi_app.)

Hey @PetrusAsikainen, thanks for writing in. We can look into wrapping wsgi_app instead of __call__ -- I assume we're doing the latter mostly because the Flask integration is pretty old and at that point it was probably the best way to hook into it. If there's nothing we'd lose/break by hooking into wsgi_app instead, we can do that.

(As a mitigation we'll probably internally monkey-patch wsgi_app.)

If you tried this -- does everything work ok?

Hey @PetrusAsikainen, thanks for writing in. We can look into wrapping wsgi_app instead of __call__ -- I assume we're doing the latter mostly because the Flask integration is pretty old and at that point it was probably the best way to hook into it. If there's nothing we'd lose/break by hooking into wsgi_app instead, we can do that.

Flask.__call__ has been an alias to it since 2010, so I'd imagine there should at most be ordering issues with other similar hooks, or someone depending on random internals of Sentry.

(As a mitigation we'll probably internally monkey-patch wsgi_app.)

If you tried this -- does everything work ok?

Mostly. We're still getting some issues not being correctly attributed to requests, but my first guess is that's because we're not correctly setting up the asyncio integration yet.

So we apparently used to patch wsgi_app but switched to __call__ in 1a66198. Unsure atm if that was just to make testing easier. FWIW the tests pass with this trivial change, except for the one linked in the old commit:

diff --git a/sentry_sdk/integrations/flask.py b/sentry_sdk/integrations/flask.py
index f45ec6db2..dfdc575ae 100644
--- a/sentry_sdk/integrations/flask.py
+++ b/sentry_sdk/integrations/flask.py
@@ -91,7 +91,7 @@ class FlaskIntegration(Integration):
         request_started.connect(_request_started)
         got_request_exception.connect(_capture_exception)
 
-        old_app = Flask.__call__
+        old_app = Flask.wsgi_app
 
         def sentry_patched_wsgi_app(self, environ, start_response):
             # type: (Any, Dict[str, str], Callable[..., Any]) -> _ScopedResponse
@@ -111,7 +111,7 @@ class FlaskIntegration(Integration):
             )
             return middleware(environ, start_response)
 
-        Flask.__call__ = sentry_patched_wsgi_app
+        Flask.wsgi_app = sentry_patched_wsgi_app

I'm recategorizing this as feature since it's a request to support a new framework

So we apparently used to patch wsgi_app but switched to __call__ in 1a66198. Unsure atm if that was just to make testing easier. FWIW the tests pass with this trivial change, except for the one linked in the old commit:

I guess that's exactly the "ordering issue" I mentioned. 1a66198 ensures that transactions are created even if a WSGI middleware is hooked at wsgi_app, and said middleware raises errors before calling the original wsgi_app.

For FlaskApp, I guess the cleanest supported workaround is to manually add SentryWsgiMiddleware to the FlaskApp.

Connexion only uses starlette.routing.Router and not the Starlette class, so I'd guess AsyncApp also doesn't work out of the box. Adding SentryAsgiMiddleware manually might work for both AsyncApp and FlaskApp, though it seems nontrivial and will raise this warning.

To make it work out of the box, one could either write a new integration for either Connexion or a2wsgi; the latter would only support FlaskApp, though.

Regarding adding tracing support for new frameworks in general, we're moving towards phasing out our home-baked Sentry tracing integrations in favor of adopting OpenTelemetry instrumentation. So we're unlikely to add a new tracing integration anytime soon. (AI integrations are an exception since they're needed in order to make AI insights work.)

I checked real quick and it looks like OTel is wrapping wsgi_app directly, so this should work once we make the switch. That being said, it'll take quite a while until we get there. We first need to get SDK v3.0 out of the door (some weeks/couple months away), which rebuilds our tracing backend with OpenTelemetry, and then we'll start working on replacing the integrations.

So for now the workaround would be using the generic WSGI/ASGI middleware.