Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/sentry_sdk/integrations/aiohttp.py: 9%
122 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import sys
2import weakref
4from sentry_sdk._compat import reraise
5from sentry_sdk.hub import Hub
6from sentry_sdk.integrations import Integration, DidNotEnable
7from sentry_sdk.integrations.logging import ignore_logger
8from sentry_sdk.integrations._wsgi_common import (
9 _filter_headers,
10 request_body_within_bounds,
11)
12from sentry_sdk.tracing import SOURCE_FOR_STYLE, Transaction
13from sentry_sdk.utils import (
14 capture_internal_exceptions,
15 event_from_exception,
16 transaction_from_function,
17 HAS_REAL_CONTEXTVARS,
18 CONTEXTVARS_ERROR_MESSAGE,
19 AnnotatedValue,
20)
22try:
23 import asyncio
25 from aiohttp import __version__ as AIOHTTP_VERSION
26 from aiohttp.web import Application, HTTPException, UrlDispatcher
27except ImportError:
28 raise DidNotEnable("AIOHTTP not installed")
30from sentry_sdk._types import MYPY
32if MYPY:
33 from aiohttp.web_request import Request
34 from aiohttp.abc import AbstractMatchInfo
35 from typing import Any
36 from typing import Dict
37 from typing import Optional
38 from typing import Tuple
39 from typing import Callable
40 from typing import Union
42 from sentry_sdk.utils import ExcInfo
43 from sentry_sdk._types import EventProcessor
46TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern")
49class AioHttpIntegration(Integration):
50 identifier = "aiohttp"
52 def __init__(self, transaction_style="handler_name"):
53 # type: (str) -> None
54 if transaction_style not in TRANSACTION_STYLE_VALUES:
55 raise ValueError(
56 "Invalid value for transaction_style: %s (must be in %s)"
57 % (transaction_style, TRANSACTION_STYLE_VALUES)
58 )
59 self.transaction_style = transaction_style
61 @staticmethod
62 def setup_once():
63 # type: () -> None
65 try:
66 version = tuple(map(int, AIOHTTP_VERSION.split(".")[:2]))
67 except (TypeError, ValueError):
68 raise DidNotEnable("AIOHTTP version unparsable: {}".format(AIOHTTP_VERSION))
70 if version < (3, 4):
71 raise DidNotEnable("AIOHTTP 3.4 or newer required.")
73 if not HAS_REAL_CONTEXTVARS:
74 # We better have contextvars or we're going to leak state between
75 # requests.
76 raise DidNotEnable(
77 "The aiohttp integration for Sentry requires Python 3.7+ "
78 " or aiocontextvars package." + CONTEXTVARS_ERROR_MESSAGE
79 )
81 ignore_logger("aiohttp.server")
83 old_handle = Application._handle
85 async def sentry_app_handle(self, request, *args, **kwargs):
86 # type: (Any, Request, *Any, **Any) -> Any
87 hub = Hub.current
88 if hub.get_integration(AioHttpIntegration) is None:
89 return await old_handle(self, request, *args, **kwargs)
91 weak_request = weakref.ref(request)
93 with Hub(hub) as hub:
94 # Scope data will not leak between requests because aiohttp
95 # create a task to wrap each request.
96 with hub.configure_scope() as scope:
97 scope.clear_breadcrumbs()
98 scope.add_event_processor(_make_request_processor(weak_request))
100 transaction = Transaction.continue_from_headers(
101 request.headers,
102 op="http.server",
103 # If this transaction name makes it to the UI, AIOHTTP's
104 # URL resolver did not find a route or died trying.
105 name="generic AIOHTTP request",
106 )
107 with hub.start_transaction(
108 transaction, custom_sampling_context={"aiohttp_request": request}
109 ):
110 try:
111 response = await old_handle(self, request)
112 except HTTPException as e:
113 transaction.set_http_status(e.status_code)
114 raise
115 except (asyncio.CancelledError, ConnectionResetError):
116 transaction.set_status("cancelled")
117 raise
118 except Exception:
119 # This will probably map to a 500 but seems like we
120 # have no way to tell. Do not set span status.
121 reraise(*_capture_exception(hub))
123 transaction.set_http_status(response.status)
124 return response
126 Application._handle = sentry_app_handle
128 old_urldispatcher_resolve = UrlDispatcher.resolve
130 async def sentry_urldispatcher_resolve(self, request):
131 # type: (UrlDispatcher, Request) -> AbstractMatchInfo
132 rv = await old_urldispatcher_resolve(self, request)
134 hub = Hub.current
135 integration = hub.get_integration(AioHttpIntegration)
137 name = None
139 try:
140 if integration.transaction_style == "handler_name":
141 name = transaction_from_function(rv.handler)
142 elif integration.transaction_style == "method_and_path_pattern":
143 route_info = rv.get_info()
144 pattern = route_info.get("path") or route_info.get("formatter")
145 name = "{} {}".format(request.method, pattern)
146 except Exception:
147 pass
149 if name is not None:
150 with Hub.current.configure_scope() as scope:
151 scope.set_transaction_name(
152 name,
153 source=SOURCE_FOR_STYLE[integration.transaction_style],
154 )
156 return rv
158 UrlDispatcher.resolve = sentry_urldispatcher_resolve
161def _make_request_processor(weak_request):
162 # type: (Callable[[], Request]) -> EventProcessor
163 def aiohttp_processor(
164 event, # type: Dict[str, Any]
165 hint, # type: Dict[str, Tuple[type, BaseException, Any]]
166 ):
167 # type: (...) -> Dict[str, Any]
168 request = weak_request()
169 if request is None:
170 return event
172 with capture_internal_exceptions():
173 request_info = event.setdefault("request", {})
175 request_info["url"] = "%s://%s%s" % (
176 request.scheme,
177 request.host,
178 request.path,
179 )
181 request_info["query_string"] = request.query_string
182 request_info["method"] = request.method
183 request_info["env"] = {"REMOTE_ADDR": request.remote}
185 hub = Hub.current
186 request_info["headers"] = _filter_headers(dict(request.headers))
188 # Just attach raw data here if it is within bounds, if available.
189 # Unfortunately there's no way to get structured data from aiohttp
190 # without awaiting on some coroutine.
191 request_info["data"] = get_aiohttp_request_data(hub, request)
193 return event
195 return aiohttp_processor
198def _capture_exception(hub):
199 # type: (Hub) -> ExcInfo
200 exc_info = sys.exc_info()
201 event, hint = event_from_exception(
202 exc_info,
203 client_options=hub.client.options, # type: ignore
204 mechanism={"type": "aiohttp", "handled": False},
205 )
206 hub.capture_event(event, hint=hint)
207 return exc_info
210BODY_NOT_READ_MESSAGE = "[Can't show request body due to implementation details.]"
213def get_aiohttp_request_data(hub, request):
214 # type: (Hub, Request) -> Union[Optional[str], AnnotatedValue]
215 bytes_body = request._read_bytes
217 if bytes_body is not None:
218 # we have body to show
219 if not request_body_within_bounds(hub.client, len(bytes_body)):
221 return AnnotatedValue(
222 "",
223 {"rem": [["!config", "x", 0, len(bytes_body)]], "len": len(bytes_body)},
224 )
225 encoding = request.charset or "utf-8"
226 return bytes_body.decode(encoding, "replace")
228 if request.can_read_body:
229 # body exists but we can't show it
230 return BODY_NOT_READ_MESSAGE
232 # request has no body
233 return None