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

1import sys 

2import weakref 

3 

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) 

21 

22try: 

23 import asyncio 

24 

25 from aiohttp import __version__ as AIOHTTP_VERSION 

26 from aiohttp.web import Application, HTTPException, UrlDispatcher 

27except ImportError: 

28 raise DidNotEnable("AIOHTTP not installed") 

29 

30from sentry_sdk._types import MYPY 

31 

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 

41 

42 from sentry_sdk.utils import ExcInfo 

43 from sentry_sdk._types import EventProcessor 

44 

45 

46TRANSACTION_STYLE_VALUES = ("handler_name", "method_and_path_pattern") 

47 

48 

49class AioHttpIntegration(Integration): 

50 identifier = "aiohttp" 

51 

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 

60 

61 @staticmethod 

62 def setup_once(): 

63 # type: () -> None 

64 

65 try: 

66 version = tuple(map(int, AIOHTTP_VERSION.split(".")[:2])) 

67 except (TypeError, ValueError): 

68 raise DidNotEnable("AIOHTTP version unparsable: {}".format(AIOHTTP_VERSION)) 

69 

70 if version < (3, 4): 

71 raise DidNotEnable("AIOHTTP 3.4 or newer required.") 

72 

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 ) 

80 

81 ignore_logger("aiohttp.server") 

82 

83 old_handle = Application._handle 

84 

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) 

90 

91 weak_request = weakref.ref(request) 

92 

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)) 

99 

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)) 

122 

123 transaction.set_http_status(response.status) 

124 return response 

125 

126 Application._handle = sentry_app_handle 

127 

128 old_urldispatcher_resolve = UrlDispatcher.resolve 

129 

130 async def sentry_urldispatcher_resolve(self, request): 

131 # type: (UrlDispatcher, Request) -> AbstractMatchInfo 

132 rv = await old_urldispatcher_resolve(self, request) 

133 

134 hub = Hub.current 

135 integration = hub.get_integration(AioHttpIntegration) 

136 

137 name = None 

138 

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 

148 

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 ) 

155 

156 return rv 

157 

158 UrlDispatcher.resolve = sentry_urldispatcher_resolve 

159 

160 

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 

171 

172 with capture_internal_exceptions(): 

173 request_info = event.setdefault("request", {}) 

174 

175 request_info["url"] = "%s://%s%s" % ( 

176 request.scheme, 

177 request.host, 

178 request.path, 

179 ) 

180 

181 request_info["query_string"] = request.query_string 

182 request_info["method"] = request.method 

183 request_info["env"] = {"REMOTE_ADDR": request.remote} 

184 

185 hub = Hub.current 

186 request_info["headers"] = _filter_headers(dict(request.headers)) 

187 

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) 

192 

193 return event 

194 

195 return aiohttp_processor 

196 

197 

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 

208 

209 

210BODY_NOT_READ_MESSAGE = "[Can't show request body due to implementation details.]" 

211 

212 

213def get_aiohttp_request_data(hub, request): 

214 # type: (Hub, Request) -> Union[Optional[str], AnnotatedValue] 

215 bytes_body = request._read_bytes 

216 

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)): 

220 

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") 

227 

228 if request.can_read_body: 

229 # body exists but we can't show it 

230 return BODY_NOT_READ_MESSAGE 

231 

232 # request has no body 

233 return None