Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/sentry_sdk/integrations/tornado.py: 10%

115 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import weakref 

2import contextlib 

3from inspect import iscoroutinefunction 

4 

5from sentry_sdk.hub import Hub, _should_send_default_pii 

6from sentry_sdk.tracing import TRANSACTION_SOURCE_COMPONENT, Transaction 

7from sentry_sdk.utils import ( 

8 HAS_REAL_CONTEXTVARS, 

9 CONTEXTVARS_ERROR_MESSAGE, 

10 event_from_exception, 

11 capture_internal_exceptions, 

12 transaction_from_function, 

13) 

14from sentry_sdk.integrations import Integration, DidNotEnable 

15from sentry_sdk.integrations._wsgi_common import ( 

16 RequestExtractor, 

17 _filter_headers, 

18 _is_json_content_type, 

19) 

20from sentry_sdk.integrations.logging import ignore_logger 

21from sentry_sdk._compat import iteritems 

22 

23try: 

24 from tornado import version_info as TORNADO_VERSION 

25 from tornado.web import RequestHandler, HTTPError 

26 from tornado.gen import coroutine 

27except ImportError: 

28 raise DidNotEnable("Tornado not installed") 

29 

30from sentry_sdk._types import MYPY 

31 

32if MYPY: 

33 from typing import Any 

34 from typing import Optional 

35 from typing import Dict 

36 from typing import Callable 

37 from typing import Generator 

38 

39 from sentry_sdk._types import EventProcessor 

40 

41 

42class TornadoIntegration(Integration): 

43 identifier = "tornado" 

44 

45 @staticmethod 

46 def setup_once(): 

47 # type: () -> None 

48 if TORNADO_VERSION < (5, 0): 

49 raise DidNotEnable("Tornado 5+ required") 

50 

51 if not HAS_REAL_CONTEXTVARS: 

52 # Tornado is async. We better have contextvars or we're going to leak 

53 # state between requests. 

54 raise DidNotEnable( 

55 "The tornado integration for Sentry requires Python 3.7+ or the aiocontextvars package" 

56 + CONTEXTVARS_ERROR_MESSAGE 

57 ) 

58 

59 ignore_logger("tornado.access") 

60 

61 old_execute = RequestHandler._execute 

62 

63 awaitable = iscoroutinefunction(old_execute) 

64 

65 if awaitable: 

66 # Starting Tornado 6 RequestHandler._execute method is a standard Python coroutine (async/await) 

67 # In that case our method should be a coroutine function too 

68 async def sentry_execute_request_handler(self, *args, **kwargs): 

69 # type: (RequestHandler, *Any, **Any) -> Any 

70 with _handle_request_impl(self): 

71 return await old_execute(self, *args, **kwargs) 

72 

73 else: 

74 

75 @coroutine # type: ignore 

76 def sentry_execute_request_handler(self, *args, **kwargs): # type: ignore 

77 # type: (RequestHandler, *Any, **Any) -> Any 

78 with _handle_request_impl(self): 

79 result = yield from old_execute(self, *args, **kwargs) 

80 return result 

81 

82 RequestHandler._execute = sentry_execute_request_handler 

83 

84 old_log_exception = RequestHandler.log_exception 

85 

86 def sentry_log_exception(self, ty, value, tb, *args, **kwargs): 

87 # type: (Any, type, BaseException, Any, *Any, **Any) -> Optional[Any] 

88 _capture_exception(ty, value, tb) 

89 return old_log_exception(self, ty, value, tb, *args, **kwargs) 

90 

91 RequestHandler.log_exception = sentry_log_exception 

92 

93 

94@contextlib.contextmanager 

95def _handle_request_impl(self): 

96 # type: (RequestHandler) -> Generator[None, None, None] 

97 hub = Hub.current 

98 integration = hub.get_integration(TornadoIntegration) 

99 

100 if integration is None: 

101 yield 

102 

103 weak_handler = weakref.ref(self) 

104 

105 with Hub(hub) as hub: 

106 with hub.configure_scope() as scope: 

107 scope.clear_breadcrumbs() 

108 processor = _make_event_processor(weak_handler) 

109 scope.add_event_processor(processor) 

110 

111 transaction = Transaction.continue_from_headers( 

112 self.request.headers, 

113 op="http.server", 

114 # Like with all other integrations, this is our 

115 # fallback transaction in case there is no route. 

116 # sentry_urldispatcher_resolve is responsible for 

117 # setting a transaction name later. 

118 name="generic Tornado request", 

119 ) 

120 

121 with hub.start_transaction( 

122 transaction, custom_sampling_context={"tornado_request": self.request} 

123 ): 

124 yield 

125 

126 

127def _capture_exception(ty, value, tb): 

128 # type: (type, BaseException, Any) -> None 

129 hub = Hub.current 

130 if hub.get_integration(TornadoIntegration) is None: 

131 return 

132 if isinstance(value, HTTPError): 

133 return 

134 

135 # If an integration is there, a client has to be there. 

136 client = hub.client # type: Any 

137 

138 event, hint = event_from_exception( 

139 (ty, value, tb), 

140 client_options=client.options, 

141 mechanism={"type": "tornado", "handled": False}, 

142 ) 

143 

144 hub.capture_event(event, hint=hint) 

145 

146 

147def _make_event_processor(weak_handler): 

148 # type: (Callable[[], RequestHandler]) -> EventProcessor 

149 def tornado_processor(event, hint): 

150 # type: (Dict[str, Any], Dict[str, Any]) -> Dict[str, Any] 

151 handler = weak_handler() 

152 if handler is None: 

153 return event 

154 

155 request = handler.request 

156 

157 with capture_internal_exceptions(): 

158 method = getattr(handler, handler.request.method.lower()) 

159 event["transaction"] = transaction_from_function(method) 

160 event["transaction_info"] = {"source": TRANSACTION_SOURCE_COMPONENT} 

161 

162 with capture_internal_exceptions(): 

163 extractor = TornadoRequestExtractor(request) 

164 extractor.extract_into_event(event) 

165 

166 request_info = event["request"] 

167 

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

169 request.protocol, 

170 request.host, 

171 request.path, 

172 ) 

173 

174 request_info["query_string"] = request.query 

175 request_info["method"] = request.method 

176 request_info["env"] = {"REMOTE_ADDR": request.remote_ip} 

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

178 

179 with capture_internal_exceptions(): 

180 if handler.current_user and _should_send_default_pii(): 

181 event.setdefault("user", {}).setdefault("is_authenticated", True) 

182 

183 return event 

184 

185 return tornado_processor 

186 

187 

188class TornadoRequestExtractor(RequestExtractor): 

189 def content_length(self): 

190 # type: () -> int 

191 if self.request.body is None: 

192 return 0 

193 return len(self.request.body) 

194 

195 def cookies(self): 

196 # type: () -> Dict[str, str] 

197 return {k: v.value for k, v in iteritems(self.request.cookies)} 

198 

199 def raw_data(self): 

200 # type: () -> bytes 

201 return self.request.body 

202 

203 def form(self): 

204 # type: () -> Dict[str, Any] 

205 return { 

206 k: [v.decode("latin1", "replace") for v in vs] 

207 for k, vs in iteritems(self.request.body_arguments) 

208 } 

209 

210 def is_json(self): 

211 # type: () -> bool 

212 return _is_json_content_type(self.request.headers.get("content-type")) 

213 

214 def files(self): 

215 # type: () -> Dict[str, Any] 

216 return {k: v[0] for k, v in iteritems(self.request.files) if v} 

217 

218 def size_of_file(self, file): 

219 # type: (Any) -> int 

220 return len(file.body or ())