Skip to content

Commit cf9eda2

Browse files
committed
feat: Prototype of session tracking
1 parent 265be22 commit cf9eda2

12 files changed

Lines changed: 231 additions & 68 deletions

File tree

sentry_sdk/consts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"attach_stacktrace": bool,
3939
"ca_certs": Optional[str],
4040
"propagate_traces": bool,
41+
"send_traces": bool,
4142
},
4243
total=False,
4344
)
@@ -71,6 +72,7 @@
7172
"attach_stacktrace": False,
7273
"ca_certs": None,
7374
"propagate_traces": True,
75+
"traces_sample_rate": 0.0,
7476
}
7577

7678

sentry_sdk/hub.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import sys
21
import copy
2+
import random
3+
import sys
34
import weakref
5+
46
from datetime import datetime
57
from contextlib import contextmanager
68
from warnings import warn
79

810
from sentry_sdk._compat import with_metaclass
911
from sentry_sdk.scope import Scope
1012
from sentry_sdk.client import Client
13+
from sentry_sdk.tracing import Span
1114
from sentry_sdk.utils import (
1215
exc_info_from_error,
1316
event_from_exception,
@@ -344,6 +347,57 @@ def add_breadcrumb(self, crumb=None, hint=None, **kwargs):
344347
while len(scope._breadcrumbs) > max_breadcrumbs:
345348
scope._breadcrumbs.popleft()
346349

350+
@contextmanager
351+
def span(self, span=None, **kwargs):
352+
if span is None:
353+
span = self.start_span(**kwargs)
354+
355+
if span is None:
356+
yield span
357+
return
358+
359+
try:
360+
yield span
361+
except Exception:
362+
span.set_tag("success", False)
363+
else:
364+
span.set_tag("success", True)
365+
finally:
366+
span.finish()
367+
self.capture_trace(span)
368+
369+
def trace(self, *args, **kwargs):
370+
return self.span(self.start_trace(*args, **kwargs))
371+
372+
def start_span(self, **kwargs):
373+
_, scope = self._stack[-1]
374+
span = scope.span
375+
if span is not None:
376+
return span.new_span(**kwargs)
377+
return None
378+
379+
def start_trace(self, transaction, **kwargs):
380+
_, scope = self._stack[-1]
381+
scope.span = span = Span.start_trace(transaction, **kwargs)
382+
return span
383+
384+
def capture_trace(self, span):
385+
if span.transaction is None:
386+
return None
387+
388+
client = self.client
389+
390+
if client is None:
391+
return None
392+
393+
sample_rate = client.options["traces_sample_rate"]
394+
if sample_rate < 1.0 and random.random() >= sample_rate:
395+
return None
396+
397+
return self.capture_event(
398+
{"type": "none", "spans": [s.to_json() for s in span._finished_spans]}
399+
)
400+
347401
@overload # noqa
348402
def push_scope(self):
349403
# type: () -> ContextManager[Scope]

sentry_sdk/integrations/celery.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from sentry_sdk.hub import Hub
1313
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
14-
from sentry_sdk.tracing import SpanContext
14+
from sentry_sdk.tracing import Span
1515
from sentry_sdk._compat import reraise
1616
from sentry_sdk.integrations import Integration
1717
from sentry_sdk.integrations.logging import ignore_logger
@@ -82,20 +82,30 @@ def _inner(*args, **kwargs):
8282
with hub.push_scope() as scope:
8383
scope._name = "celery"
8484
scope.clear_breadcrumbs()
85-
_continue_trace(args[3].get("headers") or {}, scope)
85+
span = _continue_trace(args[3].get("headers") or {}, scope)
86+
87+
with capture_internal_exceptions():
88+
scope.transaction = task.name
89+
8690
scope.add_event_processor(_make_event_processor(task, *args, **kwargs))
8791

88-
return f(*args, **kwargs)
92+
try:
93+
return f(*args, **kwargs)
94+
finally:
95+
span.finish()
96+
hub.capture_trace(span)
8997

9098
return _inner
9199

92100

93101
def _continue_trace(headers, scope):
94102
if headers:
95-
span_context = SpanContext.continue_from_headers(headers)
103+
span = Span.continue_from_headers(headers)
96104
else:
97-
span_context = SpanContext.start_trace()
98-
scope.set_span_context(span_context)
105+
span = Span.start_trace()
106+
107+
scope.span = span
108+
return span
99109

100110

101111
def _wrap_task_call(task, f):
@@ -115,9 +125,6 @@ def _inner(*args, **kwargs):
115125

116126
def _make_event_processor(task, uuid, args, kwargs, request=None):
117127
def event_processor(event, hint):
118-
with capture_internal_exceptions():
119-
event["transaction"] = task.name
120-
121128
with capture_internal_exceptions():
122129
extra = event.setdefault("extra", {})
123130
extra["celery-job"] = {

sentry_sdk/integrations/django/__init__.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,19 @@ def sentry_patched_get_response(self, request):
112112
hub = Hub.current
113113
integration = hub.get_integration(DjangoIntegration)
114114
if integration is not None:
115+
115116
with hub.configure_scope() as scope:
117+
# Rely on WSGI middleware to start a trace
118+
try:
119+
if integration.transaction_style == "function_name":
120+
scope.transaction = transaction_from_function(
121+
resolve(request.path).func
122+
)
123+
elif integration.transaction_style == "url":
124+
scope.transaction = LEGACY_RESOLVER.resolve(request.path)
125+
except Exception:
126+
pass
127+
116128
scope.add_event_processor(
117129
_make_event_processor(weakref.ref(request), integration)
118130
)
@@ -190,16 +202,6 @@ def event_processor(event, hint):
190202
if request is None:
191203
return event
192204

193-
try:
194-
if integration.transaction_style == "function_name":
195-
event["transaction"] = transaction_from_function(
196-
resolve(request.path).func
197-
)
198-
elif integration.transaction_style == "url":
199-
event["transaction"] = LEGACY_RESOLVER.resolve(request.path)
200-
except Exception:
201-
pass
202-
203205
with capture_internal_exceptions():
204206
DjangoRequestExtractor(request).extract_into_event(event)
205207

sentry_sdk/integrations/flask.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,16 @@ def _request_started(sender, **kwargs):
9999
app = _app_ctx_stack.top.app
100100
with hub.configure_scope() as scope:
101101
request = _request_ctx_stack.top.request
102+
103+
# Rely on WSGI middleware to start a trace
104+
try:
105+
if integration.transaction_style == "endpoint":
106+
scope.transaction = request.url_rule.endpoint # type: ignore
107+
elif integration.transaction_style == "url":
108+
scope.transaction = request.url_rule.rule # type: ignore
109+
except Exception:
110+
pass
111+
102112
weak_request = weakref.ref(request)
103113
scope.add_event_processor(
104114
_make_request_event_processor( # type: ignore
@@ -151,14 +161,6 @@ def inner(event, hint):
151161
if request is None:
152162
return event
153163

154-
try:
155-
if integration.transaction_style == "endpoint":
156-
event["transaction"] = request.url_rule.endpoint # type: ignore
157-
elif integration.transaction_style == "url":
158-
event["transaction"] = request.url_rule.rule # type: ignore
159-
except Exception:
160-
pass
161-
162164
with capture_internal_exceptions():
163165
FlaskRequestExtractor(request).extract_into_event(event)
164166

sentry_sdk/integrations/stdlib.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def putrequest(self, method, url, *args, **kwargs):
2929
return rv
3030

3131
self._sentrysdk_data_dict = data = {}
32+
self._sentrysdk_span = hub.start_span()
3233

3334
host = self.host
3435
port = self.port
@@ -61,6 +62,12 @@ def getresponse(self, *args, **kwargs):
6162
if "status_code" not in data:
6263
data["status_code"] = rv.status
6364
data["reason"] = rv.reason
65+
66+
span = self._sentrysdk_span
67+
if span is not None:
68+
span.set_tag("status_code", rv.status)
69+
span.finish()
70+
6471
hub.add_breadcrumb(
6572
type="http", category="httplib", data=data, hint={"httplib_response": rv}
6673
)

sentry_sdk/integrations/wsgi.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from sentry_sdk.hub import Hub, _should_send_default_pii
44
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
55
from sentry_sdk._compat import PY2, reraise, iteritems
6-
from sentry_sdk.tracing import SpanContext
6+
from sentry_sdk.tracing import Span
77
from sentry_sdk.integrations._wsgi_common import _filter_headers
88

99
if False:
@@ -78,17 +78,22 @@ def __call__(self, environ, start_response):
7878
hub = Hub(Hub.current)
7979

8080
with hub:
81+
span = None
8182
with capture_internal_exceptions():
8283
with hub.configure_scope() as scope:
8384
scope.clear_breadcrumbs()
8485
scope._name = "wsgi"
85-
scope.set_span_context(SpanContext.continue_from_environ(environ))
8686
scope.add_event_processor(_make_wsgi_event_processor(environ))
87+
scope.span = span = Span.continue_from_environ(environ)
8788

8889
try:
8990
rv = self.app(environ, start_response)
9091
except Exception:
9192
reraise(*_capture_exception(hub))
93+
finally:
94+
if span is not None:
95+
span.finish()
96+
hub.capture_trace(span)
9297

9398
return _ScopedResponse(hub, rv)
9499

sentry_sdk/scope.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,26 @@ def fingerprint(self, value):
8383
def transaction(self, value):
8484
"""When set this forces a specific transaction name to be set."""
8585
self._transaction = value
86+
if self._span:
87+
self._span.transaction = value
8688

8789
@_attr_setter
8890
def user(self, value):
8991
"""When set a specific user is bound to the scope."""
9092
self._user = value
9193

92-
def set_span_context(self, span_context):
93-
"""Sets the span context."""
94-
self._span = span_context
94+
@property
95+
def span(self):
96+
"""Get/set current tracing span."""
97+
return self._span
98+
99+
@span.setter
100+
def span(self, span):
101+
self._span = span
102+
if span.transaction:
103+
self._transaction = span.transaction
104+
elif self._transaction:
105+
span.transaction = self._transaction
95106

96107
def set_tag(self, key, value):
97108
"""Sets a tag for a key to a specific value."""

0 commit comments

Comments
 (0)