Skip to content

Commit 2cfba87

Browse files
beniwohlibasepi
authored andcommitted
flask: close the transaction in the exception handler (#635)
The `request_finished` signal isn't sent if an unhandled exception occurs, and other signals (e.g. `request_teardown` don't provide the `response` object. fixes #629
1 parent bb87169 commit 2cfba87

File tree

3 files changed

+24
-10
lines changed

3 files changed

+24
-10
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
* added instrumentation for mysql-connector and pymysql (#603)
99
* implemented stack_trace_limit configuration option (#623)
1010
* autoinsert tracing middleware in django settings (#625)
11+
12+
### Bugfixes
13+
14+
* fixed issue with transactions not being captured when errors occur in Flask (#635)
1115

1216
## v5.2.3
1317
[Check the diff](https://github.com/elastic/apm-agent-python/compare/v5.2.2...v5.2.3)

elasticapm/contrib/flask/__init__.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ def handle_exception(self, *args, **kwargs):
120120
custom={"app": self.app},
121121
handled=False,
122122
)
123+
# End the transaction here, as `request_finished` won't be called when an
124+
# unhandled exception occurs.
125+
#
126+
# Unfortunately, that also means that we can't capture any response data,
127+
# as the response isn't ready at this point in time.
128+
self.client.end_transaction(result="HTTP 5xx")
123129

124130
def init_app(self, app, **defaults):
125131
self.app = app
@@ -182,11 +188,6 @@ def request_started(self, app):
182188
else:
183189
trace_parent = None
184190
self.client.begin_transaction("request", trace_parent=trace_parent)
185-
186-
def request_finished(self, app, response):
187-
if not self.app.debug or self.client.config.debug:
188-
rule = request.url_rule.rule if request.url_rule is not None else ""
189-
rule = build_name_with_http_method_prefix(rule, request)
190191
elasticapm.set_context(
191192
lambda: get_data_from_request(
192193
request,
@@ -195,14 +196,19 @@ def request_finished(self, app, response):
195196
),
196197
"request",
197198
)
199+
rule = request.url_rule.rule if request.url_rule is not None else ""
200+
rule = build_name_with_http_method_prefix(rule, request)
201+
elasticapm.set_transaction_name(rule, override=False)
202+
203+
def request_finished(self, app, response):
204+
if not self.app.debug or self.client.config.debug:
198205
elasticapm.set_context(
199206
lambda: get_data_from_response(response, capture_headers=self.client.config.capture_headers), "response"
200207
)
201208
if response.status_code:
202209
result = "HTTP {}xx".format(response.status_code // 100)
203210
else:
204211
result = response.status
205-
elasticapm.set_transaction_name(rule, override=False)
206212
elasticapm.set_transaction_result(result, override=False)
207213
# Instead of calling end_transaction here, we defer the call until the response is closed.
208214
# This ensures that we capture things that happen until the WSGI server closes the response.

tests/contrib/flask/flask_tests.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_error_handler(flask_apm_client):
5151
client = flask_apm_client.app.test_client()
5252
response = client.get("/an-error/")
5353
assert response.status_code == 500
54-
assert len(flask_apm_client.client.events) == 1
54+
assert len(flask_apm_client.client.events[ERROR]) == 1
5555

5656
event = flask_apm_client.client.events[ERROR][0]
5757

@@ -62,12 +62,16 @@ def test_error_handler(flask_apm_client):
6262
assert exc["handled"] is False
6363
assert event["culprit"] == "tests.contrib.flask.fixtures.an_error"
6464

65+
transaction = flask_apm_client.client.events[TRANSACTION][0]
66+
assert transaction["result"] == "HTTP 5xx"
67+
assert transaction["name"] == "GET /an-error/"
68+
6569

6670
def test_get(flask_apm_client):
6771
client = flask_apm_client.app.test_client()
6872
response = client.get("/an-error/?foo=bar")
6973
assert response.status_code == 500
70-
assert len(flask_apm_client.client.events) == 1
74+
assert len(flask_apm_client.client.events[ERROR]) == 1
7175

7276
event = flask_apm_client.client.events[ERROR][0]
7377

@@ -104,7 +108,7 @@ def test_get_debug_elasticapm(flask_apm_client):
104108
flask_apm_client.client.config.debug = True
105109
with pytest.raises(ValueError):
106110
app.test_client().get("/an-error/?foo=bar")
107-
assert len(flask_apm_client.client.events) == 1
111+
assert len(flask_apm_client.client.events[ERROR]) == 1
108112

109113

110114
@pytest.mark.parametrize(
@@ -267,7 +271,7 @@ def test_post_files(flask_apm_client):
267271
},
268272
)
269273
assert response.status_code == 500
270-
assert len(flask_apm_client.client.events) == 1
274+
assert len(flask_apm_client.client.events[ERROR]) == 1
271275

272276
event = flask_apm_client.client.events[ERROR][0]
273277
if flask_apm_client.client.config.capture_body in ("errors", "all"):

0 commit comments

Comments
 (0)