Skip to content

feat(transactions): Transaction Source #1490

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 48 commits into from
Jul 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
e51cbe9
Added transaction source to flask
antonpirker Jul 11, 2022
9cfe349
Added transaction info to scope (and events)
antonpirker Jul 11, 2022
3ac05b4
Deprecated old transaction setter
antonpirker Jul 11, 2022
13f8075
Fixed linting
antonpirker Jul 11, 2022
b7be7bf
Added tests for transaction name and source in Flask.
antonpirker Jul 11, 2022
7185bfd
Renamed function for clarity
antonpirker Jul 11, 2022
1fa42c1
Set transaction source in ASGI.
antonpirker Jul 11, 2022
cc3a32b
Added tests for transaction name and source in ASGI
antonpirker Jul 11, 2022
5cf698d
Added transaction source to bottle
antonpirker Jul 12, 2022
3b94875
Improved tests for transaction info for bottle
antonpirker Jul 12, 2022
0fcf26e
Refactored tests for transaction info in Flask.
antonpirker Jul 12, 2022
c55b991
Refactored tests for transaction info for ASGI.
antonpirker Jul 12, 2022
d4792fd
Cleanup Flask
antonpirker Jul 13, 2022
eac2e8b
Cleanup ASGI
antonpirker Jul 13, 2022
5232d8a
Set transaction source in Django
antonpirker Jul 13, 2022
a8718b7
Added tests for transaction name and source in Django
antonpirker Jul 13, 2022
fbe1bd2
Added source to Transaction class
antonpirker Jul 13, 2022
66c5cff
Added transaction source to Celery
antonpirker Jul 13, 2022
1db75ac
Quick refactoring
antonpirker Jul 13, 2022
fbdf132
Added transaction source to Falcon.
antonpirker Jul 13, 2022
e1e2097
Added tests for transaction source to Falcon
antonpirker Jul 13, 2022
9f4b9b6
Added transaction source to Pyramid
antonpirker Jul 13, 2022
1125f3e
Added tests for transaction source to Pyramid.
antonpirker Jul 13, 2022
ff26d8e
Added transaction source to quart.
antonpirker Jul 13, 2022
a8251cc
Added tests for transaction source to Quart
antonpirker Jul 13, 2022
177fc76
Added transaction source and tests for Sanic
antonpirker Jul 13, 2022
4a95775
Added transaction source and tests for Tornado.,
antonpirker Jul 13, 2022
57f9c97
Added transaction source and tests for AIOHTTP
antonpirker Jul 13, 2022
b2165cc
Added transaction source and tests for Chalice
antonpirker Jul 13, 2022
1e0009b
Added transaction source and tests for GCP
antonpirker Jul 13, 2022
ddf0fe6
Added transaction source and tests for AWS lambda
antonpirker Jul 13, 2022
fa6ec70
Improved typing
antonpirker Jul 13, 2022
31b880c
Typing fixes
antonpirker Jul 13, 2022
b35dd7b
Fixed Python 2.7 error
antonpirker Jul 13, 2022
1b5d433
Added tests for transaction source for Celery
antonpirker Jul 13, 2022
33de8b8
Moved source lookup to central location to make code more dry
antonpirker Jul 13, 2022
510b8cb
Refactored setting of transaction name and source
antonpirker Jul 13, 2022
bb32ee7
Merge branch 'master' into antonpirker/1489-transaction-source
antonpirker Jul 13, 2022
e75666e
Small fixes
antonpirker Jul 13, 2022
6c937ad
Moved constants to tracing.py
antonpirker Jul 15, 2022
3fbd623
Moved new parameter to end of parameter list for backwards compatibility
antonpirker Jul 15, 2022
37c2745
Changed default to be 'route' so relay does not drop it
antonpirker Jul 15, 2022
4fae0c7
Removed return for in-place edited object.
antonpirker Jul 15, 2022
f3ba630
Performance optimisation :-)
antonpirker Jul 15, 2022
fac8652
Fixed types
antonpirker Jul 15, 2022
0458488
Merge branch 'master' into antonpirker/1489-transaction-source
antonpirker Jul 15, 2022
f919d9d
Marked async fixture (because we set strict asyncio_mode in pytest.ini
antonpirker Jul 15, 2022
8033dc0
Merge branch 'antonpirker/1489-transaction-source' of github.com:gets…
antonpirker Jul 15, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
rev: v4.3.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer

- repo: https://github.com/psf/black
rev: stable
rev: 22.6.0
hooks:
- id: black

- repo: https://gitlab.com/pycqa/flake8
rev: 4.0.1
rev: 3.9.2
hooks:
- id: flake8

Expand Down
7 changes: 5 additions & 2 deletions sentry_sdk/integrations/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
_filter_headers,
request_body_within_bounds,
)
from sentry_sdk.tracing import Transaction
from sentry_sdk.tracing import SOURCE_FOR_STYLE, Transaction
from sentry_sdk.utils import (
capture_internal_exceptions,
event_from_exception,
Expand Down Expand Up @@ -148,7 +148,10 @@ async def sentry_urldispatcher_resolve(self, request):

if name is not None:
with Hub.current.configure_scope() as scope:
scope.transaction = name
scope.set_transaction_name(
name,
source=SOURCE_FOR_STYLE[integration.transaction_style],
)

return rv

Expand Down
64 changes: 45 additions & 19 deletions sentry_sdk/integrations/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
from sentry_sdk.hub import Hub, _should_send_default_pii
from sentry_sdk.integrations._wsgi_common import _filter_headers
from sentry_sdk.sessions import auto_session_tracking
from sentry_sdk.tracing import (
SOURCE_FOR_STYLE,
TRANSACTION_SOURCE_ROUTE,
TRANSACTION_SOURCE_UNKNOWN,
)
from sentry_sdk.utils import (
ContextVar,
event_from_exception,
Expand Down Expand Up @@ -147,6 +152,7 @@ async def _run_app(self, scope, callback):
transaction = Transaction(op="asgi.server")

transaction.name = _DEFAULT_TRANSACTION_NAME
transaction.source = TRANSACTION_SOURCE_ROUTE
transaction.set_tag("asgi.type", ty)

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

if (
event.get("transaction", _DEFAULT_TRANSACTION_NAME)
== _DEFAULT_TRANSACTION_NAME
):
if self.transaction_style == "endpoint":
endpoint = asgi_scope.get("endpoint")
# Webframeworks like Starlette mutate the ASGI env once routing is
# done, which is sometime after the request has started. If we have
# an endpoint, overwrite our generic transaction name.
if endpoint:
event["transaction"] = transaction_from_function(endpoint)
elif self.transaction_style == "url":
# FastAPI includes the route object in the scope to let Sentry extract the
# path from it for the transaction name
route = asgi_scope.get("route")
if route:
path = getattr(route, "path", None)
if path is not None:
event["transaction"] = path
self._set_transaction_name_and_source(event, self.transaction_style, asgi_scope)

event["request"] = request_info

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

def _set_transaction_name_and_source(self, event, transaction_style, asgi_scope):
# type: (Event, str, Any) -> None

transaction_name_already_set = (
event.get("transaction", _DEFAULT_TRANSACTION_NAME)
!= _DEFAULT_TRANSACTION_NAME
)
if transaction_name_already_set:
return

name = ""

if transaction_style == "endpoint":
endpoint = asgi_scope.get("endpoint")
# Webframeworks like Starlette mutate the ASGI env once routing is
# done, which is sometime after the request has started. If we have
# an endpoint, overwrite our generic transaction name.
if endpoint:
name = transaction_from_function(endpoint) or ""

elif transaction_style == "url":
# FastAPI includes the route object in the scope to let Sentry extract the
# path from it for the transaction name
route = asgi_scope.get("route")
if route:
path = getattr(route, "path", None)
if path is not None:
name = path

if not name:
# If no transaction name can be found set an unknown source.
# This can happen when ASGI frameworks that are not yet supported well are used.
event["transaction_info"] = {"source": TRANSACTION_SOURCE_UNKNOWN}
return

event["transaction"] = name
event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]}

def _get_url(self, scope, default_scheme, host):
# type: (Dict[str, Any], Literal["ws", "http"], Optional[str]) -> str
"""
Expand Down
7 changes: 5 additions & 2 deletions sentry_sdk/integrations/aws_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys

from sentry_sdk.hub import Hub, _should_send_default_pii
from sentry_sdk.tracing import Transaction
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, Transaction
from sentry_sdk._compat import reraise
from sentry_sdk.utils import (
AnnotatedValue,
Expand Down Expand Up @@ -139,7 +139,10 @@ def sentry_handler(aws_event, aws_context, *args, **kwargs):
if headers is None:
headers = {}
transaction = Transaction.continue_from_headers(
headers, op="serverless.function", name=aws_context.function_name
headers,
op="serverless.function",
name=aws_context.function_name,
source=TRANSACTION_SOURCE_COMPONENT,
)
with hub.start_transaction(
transaction,
Expand Down
39 changes: 25 additions & 14 deletions sentry_sdk/integrations/bottle.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import absolute_import

from sentry_sdk.hub import Hub
from sentry_sdk.tracing import SOURCE_FOR_STYLE
from sentry_sdk.utils import (
capture_internal_exceptions,
event_from_exception,
Expand All @@ -20,7 +21,7 @@
from typing import Optional
from bottle import FileUpload, FormsDict, LocalRequest # type: ignore

from sentry_sdk._types import EventProcessor
from sentry_sdk._types import EventProcessor, Event

try:
from bottle import (
Expand All @@ -40,7 +41,7 @@
class BottleIntegration(Integration):
identifier = "bottle"

transaction_style = None
transaction_style = ""

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


def _set_transaction_name_and_source(event, transaction_style, request):
# type: (Event, str, Any) -> None
name = ""

if transaction_style == "url":
name = request.route.rule or ""

elif transaction_style == "endpoint":
name = (
request.route.name
or transaction_from_function(request.route.callback)
or ""
)

event["transaction"] = name
event["transaction_info"] = {"source": SOURCE_FOR_STYLE[transaction_style]}


def _make_request_event_processor(app, request, integration):
# type: (Bottle, LocalRequest, BottleIntegration) -> EventProcessor
def inner(event, hint):
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]

try:
if integration.transaction_style == "endpoint":
event["transaction"] = request.route.name or transaction_from_function(
request.route.callback
)
elif integration.transaction_style == "url":
event["transaction"] = request.route.rule
except Exception:
pass
def event_processor(event, hint):
# type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any]
_set_transaction_name_and_source(event, integration.transaction_style, request)

with capture_internal_exceptions():
BottleRequestExtractor(request).extract_into_event(event)

return event

return inner
return event_processor
8 changes: 6 additions & 2 deletions sentry_sdk/integrations/celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
import sys

from sentry_sdk.hub import Hub
from sentry_sdk.utils import capture_internal_exceptions, event_from_exception
from sentry_sdk.tracing import TRANSACTION_SOURCE_TASK
from sentry_sdk.utils import (
capture_internal_exceptions,
event_from_exception,
)
from sentry_sdk.tracing import Transaction
from sentry_sdk._compat import reraise
from sentry_sdk.integrations import Integration, DidNotEnable
Expand Down Expand Up @@ -154,8 +158,8 @@ def _inner(*args, **kwargs):
args[3].get("headers") or {},
op="celery.task",
name="unknown celery task",
source=TRANSACTION_SOURCE_TASK,
)

transaction.name = task.name
transaction.set_status("ok")

Expand Down
7 changes: 6 additions & 1 deletion sentry_sdk/integrations/chalice.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from sentry_sdk.hub import Hub
from sentry_sdk.integrations import Integration, DidNotEnable
from sentry_sdk.integrations.aws_lambda import _make_request_event_processor
from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT
from sentry_sdk.utils import (
capture_internal_exceptions,
event_from_exception,
Expand Down Expand Up @@ -65,7 +66,11 @@ def wrapped_view_function(**function_args):
with hub.push_scope() as scope:
with capture_internal_exceptions():
configured_time = app.lambda_context.get_remaining_time_in_millis()
scope.transaction = app.lambda_context.function_name
scope.set_transaction_name(
app.lambda_context.function_name,
source=TRANSACTION_SOURCE_COMPONENT,
)

scope.add_event_processor(
_make_request_event_processor(
app.current_request.to_dict(),
Expand Down
56 changes: 34 additions & 22 deletions sentry_sdk/integrations/django/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from sentry_sdk.hub import Hub, _should_send_default_pii
from sentry_sdk.scope import add_global_event_processor
from sentry_sdk.serializer import add_global_repr_processor
from sentry_sdk.tracing import SOURCE_FOR_STYLE
from sentry_sdk.tracing_utils import record_sql_queries
from sentry_sdk.utils import (
HAS_REAL_CONTEXTVARS,
Expand Down Expand Up @@ -82,7 +83,7 @@ def is_authenticated(request_user):
class DjangoIntegration(Integration):
identifier = "django"

transaction_style = None
transaction_style = ""
middleware_spans = None

def __init__(self, transaction_style="url", middleware_spans=True):
Expand Down Expand Up @@ -319,6 +320,32 @@ def _patch_django_asgi_handler():
patch_django_asgi_handler_impl(ASGIHandler)


def _set_transaction_name_and_source(scope, transaction_style, request):
# type: (Scope, str, WSGIRequest) -> None
try:
transaction_name = ""
if transaction_style == "function_name":
fn = resolve(request.path).func
transaction_name = (
transaction_from_function(getattr(fn, "view_class", fn)) or ""
)

elif transaction_style == "url":
if hasattr(request, "urlconf"):
transaction_name = LEGACY_RESOLVER.resolve(
request.path_info, urlconf=request.urlconf
)
else:
transaction_name = LEGACY_RESOLVER.resolve(request.path_info)

scope.set_transaction_name(
transaction_name,
source=SOURCE_FOR_STYLE[transaction_style],
)
except Exception:
pass


def _before_get_response(request):
# type: (WSGIRequest) -> None
hub = Hub.current
Expand All @@ -330,24 +357,15 @@ def _before_get_response(request):

with hub.configure_scope() as scope:
# Rely on WSGI middleware to start a trace
try:
if integration.transaction_style == "function_name":
fn = resolve(request.path).func
scope.transaction = transaction_from_function(
getattr(fn, "view_class", fn)
)
elif integration.transaction_style == "url":
scope.transaction = LEGACY_RESOLVER.resolve(request.path_info)
except Exception:
pass
_set_transaction_name_and_source(scope, integration.transaction_style, request)

scope.add_event_processor(
_make_event_processor(weakref.ref(request), integration)
)


def _attempt_resolve_again(request, scope):
# type: (WSGIRequest, Scope) -> None
def _attempt_resolve_again(request, scope, transaction_style):
# type: (WSGIRequest, Scope, str) -> None
"""
Some django middlewares overwrite request.urlconf
so we need to respect that contract,
Expand All @@ -356,13 +374,7 @@ def _attempt_resolve_again(request, scope):
if not hasattr(request, "urlconf"):
return

try:
scope.transaction = LEGACY_RESOLVER.resolve(
request.path_info,
urlconf=request.urlconf,
)
except Exception:
pass
_set_transaction_name_and_source(scope, transaction_style, request)


def _after_get_response(request):
Expand All @@ -373,7 +385,7 @@ def _after_get_response(request):
return

with hub.configure_scope() as scope:
_attempt_resolve_again(request, scope)
_attempt_resolve_again(request, scope, integration.transaction_style)


def _patch_get_response():
Expand Down Expand Up @@ -438,7 +450,7 @@ def _got_request_exception(request=None, **kwargs):

if request is not None and integration.transaction_style == "url":
with hub.configure_scope() as scope:
_attempt_resolve_again(request, scope)
_attempt_resolve_again(request, scope, integration.transaction_style)

# If an integration is there, a client has to be there.
client = hub.client # type: Any
Expand Down
Loading