150 lines
4.9 KiB
Python
150 lines
4.9 KiB
Python
import sys
|
|
import weakref
|
|
|
|
from sentry_sdk._compat import reraise
|
|
from sentry_sdk.hub import Hub
|
|
from sentry_sdk.integrations import Integration
|
|
from sentry_sdk.integrations.logging import ignore_logger
|
|
from sentry_sdk.integrations._wsgi_common import _filter_headers
|
|
from sentry_sdk.utils import (
|
|
capture_internal_exceptions,
|
|
event_from_exception,
|
|
transaction_from_function,
|
|
HAS_REAL_CONTEXTVARS,
|
|
)
|
|
|
|
import asyncio
|
|
from aiohttp.web import Application, HTTPException, UrlDispatcher # type: ignore
|
|
|
|
from sentry_sdk._types import MYPY
|
|
|
|
if MYPY:
|
|
from aiohttp.web_request import Request # type: ignore
|
|
from aiohttp.abc import AbstractMatchInfo # type: ignore
|
|
from typing import Any
|
|
from typing import Dict
|
|
from typing import Tuple
|
|
from typing import Callable
|
|
|
|
from sentry_sdk.utils import ExcInfo
|
|
from sentry_sdk._types import EventProcessor
|
|
|
|
|
|
class AioHttpIntegration(Integration):
|
|
identifier = "aiohttp"
|
|
|
|
@staticmethod
|
|
def setup_once():
|
|
# type: () -> None
|
|
if not HAS_REAL_CONTEXTVARS:
|
|
# We better have contextvars or we're going to leak state between
|
|
# requests.
|
|
raise RuntimeError(
|
|
"The aiohttp integration for Sentry requires Python 3.7+ "
|
|
" or aiocontextvars package"
|
|
)
|
|
|
|
ignore_logger("aiohttp.server")
|
|
|
|
old_handle = Application._handle
|
|
|
|
async def sentry_app_handle(self, request, *args, **kwargs):
|
|
# type: (Any, Request, *Any, **Any) -> Any
|
|
async def inner():
|
|
# type: () -> Any
|
|
hub = Hub.current
|
|
if hub.get_integration(AioHttpIntegration) is None:
|
|
return await old_handle(self, request, *args, **kwargs)
|
|
|
|
weak_request = weakref.ref(request)
|
|
|
|
with Hub(Hub.current) as hub:
|
|
with hub.configure_scope() as scope:
|
|
scope.clear_breadcrumbs()
|
|
scope.add_event_processor(_make_request_processor(weak_request))
|
|
|
|
# If this transaction name makes it to the UI, AIOHTTP's
|
|
# URL resolver did not find a route or died trying.
|
|
with hub.start_span(transaction="generic AIOHTTP request"):
|
|
try:
|
|
response = await old_handle(self, request)
|
|
except HTTPException:
|
|
raise
|
|
except Exception:
|
|
reraise(*_capture_exception(hub))
|
|
|
|
return response
|
|
|
|
# Explicitly wrap in task such that current contextvar context is
|
|
# copied. Just doing `return await inner()` will leak scope data
|
|
# between requests.
|
|
return await asyncio.get_event_loop().create_task(inner())
|
|
|
|
Application._handle = sentry_app_handle
|
|
|
|
old_urldispatcher_resolve = UrlDispatcher.resolve
|
|
|
|
async def sentry_urldispatcher_resolve(self, request):
|
|
# type: (UrlDispatcher, Request) -> AbstractMatchInfo
|
|
rv = await old_urldispatcher_resolve(self, request)
|
|
|
|
name = None
|
|
|
|
try:
|
|
name = transaction_from_function(rv.handler)
|
|
except Exception:
|
|
pass
|
|
|
|
if name is not None:
|
|
with Hub.current.configure_scope() as scope:
|
|
scope.transaction = name
|
|
|
|
return rv
|
|
|
|
UrlDispatcher.resolve = sentry_urldispatcher_resolve
|
|
|
|
|
|
def _make_request_processor(weak_request):
|
|
# type: (Callable[[], Request]) -> EventProcessor
|
|
def aiohttp_processor(
|
|
event, # type: Dict[str, Any]
|
|
hint, # type: Dict[str, Tuple[type, BaseException, Any]]
|
|
):
|
|
# type: (...) -> Dict[str, Any]
|
|
request = weak_request()
|
|
if request is None:
|
|
return event
|
|
|
|
with capture_internal_exceptions():
|
|
# TODO: Figure out what to do with request body. Methods on request
|
|
# are async, but event processors are not.
|
|
|
|
request_info = event.setdefault("request", {})
|
|
|
|
request_info["url"] = "%s://%s%s" % (
|
|
request.scheme,
|
|
request.host,
|
|
request.path,
|
|
)
|
|
|
|
request_info["query_string"] = request.query_string
|
|
request_info["method"] = request.method
|
|
request_info["env"] = {"REMOTE_ADDR": request.remote}
|
|
request_info["headers"] = _filter_headers(dict(request.headers))
|
|
|
|
return event
|
|
|
|
return aiohttp_processor
|
|
|
|
|
|
def _capture_exception(hub):
|
|
# type: (Hub) -> ExcInfo
|
|
exc_info = sys.exc_info()
|
|
event, hint = event_from_exception(
|
|
exc_info,
|
|
client_options=hub.client.options, # type: ignore
|
|
mechanism={"type": "aiohttp", "handled": False},
|
|
)
|
|
hub.capture_event(event, hint=hint)
|
|
return exc_info
|