Skip to content

Commit d4bc0f8

Browse files
authored
feat(transactions): Transaction Source (#1490)
Added transaction source (plus tests) to the following Integrations: Flask, ASGI, Bottle, Django, Celery, Falcon, Pyramid, Quart, Sanic, Tornado, AIOHTTP, Chalice, GCP, AWS Lambda,
1 parent b076a78 commit d4bc0f8

31 files changed

+613
-166
lines changed

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
# See https://pre-commit.com/hooks.html for more hooks
33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v3.2.0
5+
rev: v4.3.0
66
hooks:
77
- id: trailing-whitespace
88
- id: end-of-file-fixer
99

1010
- repo: https://github.com/psf/black
11-
rev: stable
11+
rev: 22.6.0
1212
hooks:
1313
- id: black
1414

1515
- repo: https://gitlab.com/pycqa/flake8
16-
rev: 4.0.1
16+
rev: 3.9.2
1717
hooks:
1818
- id: flake8
1919

sentry_sdk/integrations/aiohttp.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
_filter_headers,
1010
request_body_within_bounds,
1111
)
12-
from sentry_sdk.tracing import Transaction
12+
from sentry_sdk.tracing import SOURCE_FOR_STYLE, Transaction
1313
from sentry_sdk.utils import (
1414
capture_internal_exceptions,
1515
event_from_exception,
@@ -148,7 +148,10 @@ async def sentry_urldispatcher_resolve(self, request):
148148

149149
if name is not None:
150150
with Hub.current.configure_scope() as scope:
151-
scope.transaction = name
151+
scope.set_transaction_name(
152+
name,
153+
source=SOURCE_FOR_STYLE[integration.transaction_style],
154+
)
152155

153156
return rv
154157

sentry_sdk/integrations/asgi.py

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
from sentry_sdk.hub import Hub, _should_send_default_pii
1414
from sentry_sdk.integrations._wsgi_common import _filter_headers
1515
from sentry_sdk.sessions import auto_session_tracking
16+
from sentry_sdk.tracing import (
17+
SOURCE_FOR_STYLE,
18+
TRANSACTION_SOURCE_ROUTE,
19+
TRANSACTION_SOURCE_UNKNOWN,
20+
)
1621
from sentry_sdk.utils import (
1722
ContextVar,
1823
event_from_exception,
@@ -147,6 +152,7 @@ async def _run_app(self, scope, callback):
147152
transaction = Transaction(op="asgi.server")
148153

149154
transaction.name = _DEFAULT_TRANSACTION_NAME
155+
transaction.source = TRANSACTION_SOURCE_ROUTE
150156
transaction.set_tag("asgi.type", ty)
151157

152158
with hub.start_transaction(
@@ -183,25 +189,7 @@ def event_processor(self, event, hint, asgi_scope):
183189
if client and _should_send_default_pii():
184190
request_info["env"] = {"REMOTE_ADDR": self._get_ip(asgi_scope)}
185191

186-
if (
187-
event.get("transaction", _DEFAULT_TRANSACTION_NAME)
188-
== _DEFAULT_TRANSACTION_NAME
189-
):
190-
if self.transaction_style == "endpoint":
191-
endpoint = asgi_scope.get("endpoint")
192-
# Webframeworks like Starlette mutate the ASGI env once routing is
193-
# done, which is sometime after the request has started. If we have
194-
# an endpoint, overwrite our generic transaction name.
195-
if endpoint:
196-
event["transaction"] = transaction_from_function(endpoint)
197-
elif self.transaction_style == "url":
198-
# FastAPI includes the route object in the scope to let Sentry extract the
199-
# path from it for the transaction name
200-
route = asgi_scope.get("route")
201-
if route:
202-
path = getattr(route, "path", None)
203-
if path is not None:
204-
event["transaction"] = path
192+
self._set_transaction_name_and_source(event, self.transaction_style, asgi_scope)
205193

206194
event["request"] = request_info
207195

@@ -213,6 +201,44 @@ def event_processor(self, event, hint, asgi_scope):
213201
# data to your liking it's recommended to use the `before_send` callback
214202
# for that.
215203

204+
def _set_transaction_name_and_source(self, event, transaction_style, asgi_scope):
205+
# type: (Event, str, Any) -> None
206+
207+
transaction_name_already_set = (
208+
event.get("transaction", _DEFAULT_TRANSACTION_NAME)
209+
!= _DEFAULT_TRANSACTION_NAME
210+
)
211+
if transaction_name_already_set:
212+
return
213+
214+
name = ""
215+
216+
if transaction_style == "endpoint":
217+
endpoint = asgi_scope.get("endpoint")
218+
# Webframeworks like Starlette mutate the ASGI env once routing is
219+
# done, which is sometime after the request has started. If we have
220+
# an endpoint, overwrite our generic transaction name.
221+
if endpoint:
222+
name = transaction_from_function(endpoint) or ""
223+
224+
elif transaction_style == "url":
225+
# FastAPI includes the route object in the scope to let Sentry extract the
226+
# path from it for the transaction name
227+
route = asgi_scope.get("route")
228+
if route:
229+
path = getattr(route, "path", None)
230+
if path is not None:
231+
name = path
232+
233+
if not name:
234+
# If no transaction name can be found set an unknown source.
235+
# This can happen when ASGI frameworks that are not yet supported well are used.
236+
event["transaction_info"] = {"source": TRANSACTION_SOURCE_UNKNOWN}
237+
return
238+
239+
event["transaction"] = name
240+
event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]}
241+
216242
def _get_url(self, scope, default_scheme, host):
217243
# type: (Dict[str, Any], Literal["ws", "http"], Optional[str]) -> str
218244
"""

sentry_sdk/integrations/aws_lambda.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44

55
from sentry_sdk.hub import Hub, _should_send_default_pii
6-
from sentry_sdk.tracing import Transaction
6+
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, Transaction
77
from sentry_sdk._compat import reraise
88
from sentry_sdk.utils import (
99
AnnotatedValue,
@@ -139,7 +139,10 @@ def sentry_handler(aws_event, aws_context, *args, **kwargs):
139139
if headers is None:
140140
headers = {}
141141
transaction = Transaction.continue_from_headers(
142-
headers, op="serverless.function", name=aws_context.function_name
142+
headers,
143+
op="serverless.function",
144+
name=aws_context.function_name,
145+
source=TRANSACTION_SOURCE_COMPONENT,
143146
)
144147
with hub.start_transaction(
145148
transaction,

sentry_sdk/integrations/bottle.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import absolute_import
22

33
from sentry_sdk.hub import Hub
4+
from sentry_sdk.tracing import SOURCE_FOR_STYLE
45
from sentry_sdk.utils import (
56
capture_internal_exceptions,
67
event_from_exception,
@@ -20,7 +21,7 @@
2021
from typing import Optional
2122
from bottle import FileUpload, FormsDict, LocalRequest # type: ignore
2223

23-
from sentry_sdk._types import EventProcessor
24+
from sentry_sdk._types import EventProcessor, Event
2425

2526
try:
2627
from bottle import (
@@ -40,7 +41,7 @@
4041
class BottleIntegration(Integration):
4142
identifier = "bottle"
4243

43-
transaction_style = None
44+
transaction_style = ""
4445

4546
def __init__(self, transaction_style="endpoint"):
4647
# type: (str) -> None
@@ -176,24 +177,34 @@ def size_of_file(self, file):
176177
return file.content_length
177178

178179

180+
def _set_transaction_name_and_source(event, transaction_style, request):
181+
# type: (Event, str, Any) -> None
182+
name = ""
183+
184+
if transaction_style == "url":
185+
name = request.route.rule or ""
186+
187+
elif transaction_style == "endpoint":
188+
name = (
189+
request.route.name
190+
or transaction_from_function(request.route.callback)
191+
or ""
192+
)
193+
194+
event["transaction"] = name
195+
event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]}
196+
197+
179198
def _make_request_event_processor(app, request, integration):
180199
# type: (Bottle, LocalRequest, BottleIntegration) -> EventProcessor
181-
def inner(event, hint):
182-
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
183200

184-
try:
185-
if integration.transaction_style == "endpoint":
186-
event["transaction"] = request.route.name or transaction_from_function(
187-
request.route.callback
188-
)
189-
elif integration.transaction_style == "url":
190-
event["transaction"] = request.route.rule
191-
except Exception:
192-
pass
201+
def event_processor(event, hint):
202+
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
203+
_set_transaction_name_and_source(event, integration.transaction_style, request)
193204

194205
with capture_internal_exceptions():
195206
BottleRequestExtractor(request).extract_into_event(event)
196207

197208
return event
198209

199-
return inner
210+
return event_processor

sentry_sdk/integrations/celery.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import sys
44

55
from sentry_sdk.hub import Hub
6-
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
6+
from sentry_sdk.tracing import TRANSACTION_SOURCE_TASK
7+
from sentry_sdk.utils import (
8+
capture_internal_exceptions,
9+
event_from_exception,
10+
)
711
from sentry_sdk.tracing import Transaction
812
from sentry_sdk._compat import reraise
913
from sentry_sdk.integrations import Integration, DidNotEnable
@@ -154,8 +158,8 @@ def _inner(*args, **kwargs):
154158
args[3].get("headers") or {},
155159
op="celery.task",
156160
name="unknown celery task",
161+
source=TRANSACTION_SOURCE_TASK,
157162
)
158-
159163
transaction.name = task.name
160164
transaction.set_status("ok")
161165

sentry_sdk/integrations/chalice.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from sentry_sdk.hub import Hub
55
from sentry_sdk.integrations import Integration, DidNotEnable
66
from sentry_sdk.integrations.aws_lambda import _make_request_event_processor
7+
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT
78
from sentry_sdk.utils import (
89
capture_internal_exceptions,
910
event_from_exception,
@@ -65,7 +66,11 @@ def wrapped_view_function(**function_args):
6566
with hub.push_scope() as scope:
6667
with capture_internal_exceptions():
6768
configured_time = app.lambda_context.get_remaining_time_in_millis()
68-
scope.transaction = app.lambda_context.function_name
69+
scope.set_transaction_name(
70+
app.lambda_context.function_name,
71+
source=TRANSACTION_SOURCE_COMPONENT,
72+
)
73+
6974
scope.add_event_processor(
7075
_make_request_event_processor(
7176
app.current_request.to_dict(),

sentry_sdk/integrations/django/__init__.py

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from sentry_sdk.hub import Hub, _should_send_default_pii
1010
from sentry_sdk.scope import add_global_event_processor
1111
from sentry_sdk.serializer import add_global_repr_processor
12+
from sentry_sdk.tracing import SOURCE_FOR_STYLE
1213
from sentry_sdk.tracing_utils import record_sql_queries
1314
from sentry_sdk.utils import (
1415
HAS_REAL_CONTEXTVARS,
@@ -82,7 +83,7 @@ def is_authenticated(request_user):
8283
class DjangoIntegration(Integration):
8384
identifier = "django"
8485

85-
transaction_style = None
86+
transaction_style = ""
8687
middleware_spans = None
8788

8889
def __init__(self, transaction_style="url", middleware_spans=True):
@@ -319,6 +320,32 @@ def _patch_django_asgi_handler():
319320
patch_django_asgi_handler_impl(ASGIHandler)
320321

321322

323+
def _set_transaction_name_and_source(scope, transaction_style, request):
324+
# type: (Scope, str, WSGIRequest) -> None
325+
try:
326+
transaction_name = ""
327+
if transaction_style == "function_name":
328+
fn = resolve(request.path).func
329+
transaction_name = (
330+
transaction_from_function(getattr(fn, "view_class", fn)) or ""
331+
)
332+
333+
elif transaction_style == "url":
334+
if hasattr(request, "urlconf"):
335+
transaction_name = LEGACY_RESOLVER.resolve(
336+
request.path_info, urlconf=request.urlconf
337+
)
338+
else:
339+
transaction_name = LEGACY_RESOLVER.resolve(request.path_info)
340+
341+
scope.set_transaction_name(
342+
transaction_name,
343+
source=SOURCE_FOR_STYLE[transaction_style],
344+
)
345+
except Exception:
346+
pass
347+
348+
322349
def _before_get_response(request):
323350
# type: (WSGIRequest) -> None
324351
hub = Hub.current
@@ -330,24 +357,15 @@ def _before_get_response(request):
330357

331358
with hub.configure_scope() as scope:
332359
# Rely on WSGI middleware to start a trace
333-
try:
334-
if integration.transaction_style == "function_name":
335-
fn = resolve(request.path).func
336-
scope.transaction = transaction_from_function(
337-
getattr(fn, "view_class", fn)
338-
)
339-
elif integration.transaction_style == "url":
340-
scope.transaction = LEGACY_RESOLVER.resolve(request.path_info)
341-
except Exception:
342-
pass
360+
_set_transaction_name_and_source(scope, integration.transaction_style, request)
343361

344362
scope.add_event_processor(
345363
_make_event_processor(weakref.ref(request), integration)
346364
)
347365

348366

349-
def _attempt_resolve_again(request, scope):
350-
# type: (WSGIRequest, Scope) -> None
367+
def _attempt_resolve_again(request, scope, transaction_style):
368+
# type: (WSGIRequest, Scope, str) -> None
351369
"""
352370
Some django middlewares overwrite request.urlconf
353371
so we need to respect that contract,
@@ -356,13 +374,7 @@ def _attempt_resolve_again(request, scope):
356374
if not hasattr(request, "urlconf"):
357375
return
358376

359-
try:
360-
scope.transaction = LEGACY_RESOLVER.resolve(
361-
request.path_info,
362-
urlconf=request.urlconf,
363-
)
364-
except Exception:
365-
pass
377+
_set_transaction_name_and_source(scope, transaction_style, request)
366378

367379

368380
def _after_get_response(request):
@@ -373,7 +385,7 @@ def _after_get_response(request):
373385
return
374386

375387
with hub.configure_scope() as scope:
376-
_attempt_resolve_again(request, scope)
388+
_attempt_resolve_again(request, scope, integration.transaction_style)
377389

378390

379391
def _patch_get_response():
@@ -438,7 +450,7 @@ def _got_request_exception(request=None, **kwargs):
438450

439451
if request is not None and integration.transaction_style == "url":
440452
with hub.configure_scope() as scope:
441-
_attempt_resolve_again(request, scope)
453+
_attempt_resolve_again(request, scope, integration.transaction_style)
442454

443455
# If an integration is there, a client has to be there.
444456
client = hub.client # type: Any

0 commit comments

Comments
 (0)