Skip to content

Commit eb947ef

Browse files
authored
feat: Add status to transactions (getsentry#465)
* feat: Add status to transactions * fix: Fix celery tests * fix: Revert added tags * fix: Fix tests * fix: Fix tests
1 parent e6395de commit eb947ef

File tree

9 files changed

+96
-14
lines changed

9 files changed

+96
-14
lines changed

sentry_sdk/hub.py

-2
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,6 @@ def span(
448448
except Exception:
449449
span.set_failure()
450450
raise
451-
else:
452-
span.set_success()
453451
finally:
454452
try:
455453
span.finish()

sentry_sdk/integrations/celery.py

+4
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ def _capture_exception(task, exc_info):
185185

186186
hub.capture_event(event, hint=hint)
187187

188+
with capture_internal_exceptions():
189+
with hub.configure_scope() as scope:
190+
scope.span.set_failure()
191+
188192

189193
def _patch_worker_exit():
190194
# Need to flush queue before worker shutdown because a crashing worker will

sentry_sdk/integrations/sqlalchemy.py

-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def _before_cursor_execute(
4949
span = ctx_mgr.__enter__()
5050

5151
if span is not None:
52-
span.set_success() # might be overwritten later
5352
conn._sentry_sql_span = span
5453

5554

sentry_sdk/integrations/wsgi.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import functools
12
import sys
23

34
from sentry_sdk.hub import Hub, _should_send_default_pii
@@ -16,9 +17,14 @@
1617
from typing import Any
1718
from typing import Tuple
1819
from typing import Optional
20+
from typing import TypeVar
1921

2022
from sentry_sdk.utils import ExcInfo
2123

24+
T = TypeVar("T")
25+
U = TypeVar("U")
26+
E = TypeVar("E")
27+
2228

2329
if PY2:
2430

@@ -90,15 +96,31 @@ def __call__(self, environ, start_response):
9096
span.op = "http.server"
9197
span.transaction = "generic WSGI request"
9298

93-
with hub.span(span):
99+
with hub.span(span) as span:
94100
try:
95-
rv = self.app(environ, start_response)
101+
rv = self.app(
102+
environ,
103+
functools.partial(_sentry_start_response, start_response, span),
104+
)
96105
except BaseException:
97106
reraise(*_capture_exception(hub))
98107

99108
return _ScopedResponse(hub, rv)
100109

101110

111+
def _sentry_start_response(
112+
old_start_response, span, status, response_headers, exc_info=None
113+
):
114+
# type: (Callable[[str, U, Optional[E]], T], Span, str, U, Optional[E]) -> T
115+
with capture_internal_exceptions():
116+
status_int = int(status.split(" ", 1)[0])
117+
span.set_tag("http.status_code", status_int)
118+
if 500 <= status_int < 600:
119+
span.set_failure()
120+
121+
return old_start_response(status, response_headers, exc_info)
122+
123+
102124
def _get_environ(environ):
103125
# type: (Dict[str, str]) -> Iterator[Tuple[str, str]]
104126
"""

sentry_sdk/tracing.py

+15-5
Original file line numberDiff line numberDiff line change
@@ -192,18 +192,21 @@ def set_data(self, key, value):
192192
self._data[key] = value
193193

194194
def set_failure(self):
195-
self.set_tag("error", True)
195+
self.set_tag("status", "failure")
196196

197197
def set_success(self):
198-
self.set_tag("error", False)
198+
self.set_tag("status", "success")
199+
200+
def is_success(self):
201+
return self._tags.get("status") in (None, "success")
199202

200203
def finish(self):
201204
self.timestamp = datetime.now()
202205
if self._finished_spans is not None:
203206
self._finished_spans.append(self)
204207

205208
def to_json(self):
206-
return {
209+
rv = {
207210
"trace_id": self.trace_id,
208211
"span_id": self.span_id,
209212
"parent_span_id": self.parent_span_id,
@@ -217,15 +220,22 @@ def to_json(self):
217220
"data": self._data,
218221
}
219222

223+
return rv
224+
220225
def get_trace_context(self):
221-
return {
226+
rv = {
222227
"trace_id": self.trace_id,
223228
"span_id": self.span_id,
224229
"parent_span_id": self.parent_span_id,
225230
"op": self.op,
226231
"description": self.description,
227232
}
228233

234+
if "status" in self._tags:
235+
rv["status"] = self._tags["status"]
236+
237+
return rv
238+
229239

230240
def _format_sql(cursor, sql):
231241
# type: (Any, str) -> Optional[str]
@@ -296,7 +306,7 @@ def record_http_request(hub, url, method):
296306
def maybe_create_breadcrumbs_from_span(hub, span):
297307
if span.op == "redis":
298308
hub.add_breadcrumb(type="redis", category="redis", data=span._tags)
299-
elif span.op == "http" and not span._tags.get("error"):
309+
elif span.op == "http" and span.is_success():
300310
hub.add_breadcrumb(
301311
type="http",
302312
category="httplib",

tests/integrations/celery/test_celery.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ def dummy_task(x, y):
123123
assert execution_event["contexts"]["trace"]["trace_id"] == span.trace_id
124124
assert submission_event["contexts"]["trace"]["trace_id"] == span.trace_id
125125

126+
if task_fails:
127+
assert execution_event["contexts"]["trace"]["status"] == "failure"
128+
else:
129+
assert "status" not in execution_event["contexts"]["trace"]
130+
126131
assert execution_event["spans"] == []
127132
assert submission_event["spans"] == [
128133
{
@@ -133,7 +138,7 @@ def dummy_task(x, y):
133138
u"same_process_as_parent": True,
134139
u"span_id": submission_event["spans"][0]["span_id"],
135140
u"start_timestamp": submission_event["spans"][0]["start_timestamp"],
136-
u"tags": {u"error": False},
141+
u"tags": {},
137142
u"timestamp": submission_event["spans"][0]["timestamp"],
138143
u"trace_id": text_type(span.trace_id),
139144
}

tests/integrations/flask/test_flask.py

+44
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,50 @@ def zerodivision(e):
557557
assert not events
558558

559559

560+
def test_tracing_success(sentry_init, capture_events, app):
561+
sentry_init(traces_sample_rate=1.0, integrations=[flask_sentry.FlaskIntegration()])
562+
563+
events = capture_events()
564+
565+
with app.test_client() as client:
566+
response = client.get("/message")
567+
assert response.status_code == 200
568+
569+
message_event, transaction_event = events
570+
571+
assert transaction_event["type"] == "transaction"
572+
assert transaction_event["transaction"] == "hi"
573+
assert "status" not in transaction_event["contexts"]["trace"]
574+
575+
assert message_event["message"] == "hi"
576+
assert message_event["transaction"] == "hi"
577+
578+
579+
def test_tracing_error(sentry_init, capture_events, app):
580+
sentry_init(traces_sample_rate=1.0, integrations=[flask_sentry.FlaskIntegration()])
581+
582+
events = capture_events()
583+
584+
@app.route("/error")
585+
def error():
586+
1 / 0
587+
588+
with pytest.raises(ZeroDivisionError):
589+
with app.test_client() as client:
590+
response = client.get("/error")
591+
assert response.status_code == 500
592+
593+
error_event, transaction_event = events
594+
595+
assert transaction_event["type"] == "transaction"
596+
assert transaction_event["transaction"] == "error"
597+
assert transaction_event["contexts"]["trace"]["status"] == "failure"
598+
599+
assert error_event["transaction"] == "error"
600+
exception, = error_event["exception"]["values"]
601+
assert exception["type"] == "ZeroDivisionError"
602+
603+
560604
def test_class_based_views(sentry_init, app, capture_events):
561605
sentry_init(integrations=[flask_sentry.FlaskIntegration()])
562606
events = capture_events()

tests/integrations/redis/test_redis.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def test_basic(sentry_init, capture_events):
1818

1919
assert crumb == {
2020
"category": "redis",
21-
"data": {"error": False, "redis.key": "foobar"},
21+
"data": {"redis.key": "foobar"},
2222
"timestamp": crumb["timestamp"],
2323
"type": "redis",
2424
}

tests/test_tracing.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ def test_basic(sentry_init, capture_events, sample_rate):
2525

2626
span1, span2 = event["spans"]
2727
parent_span = event
28-
assert span1["tags"]["error"]
28+
assert span1["tags"]["status"] == "failure"
2929
assert span1["op"] == "foo"
3030
assert span1["description"] == "foodesc"
31-
assert not span2["tags"]["error"]
31+
assert "status" not in span2["tags"]
3232
assert span2["op"] == "bar"
3333
assert span2["description"] == "bardesc"
3434
assert parent_span["transaction"] == "hi"

0 commit comments

Comments
 (0)