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

98 statements  

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

1from __future__ import absolute_import 

2 

3import logging 

4import datetime 

5from fnmatch import fnmatch 

6 

7from sentry_sdk.hub import Hub 

8from sentry_sdk.utils import ( 

9 to_string, 

10 event_from_exception, 

11 current_stacktrace, 

12 capture_internal_exceptions, 

13) 

14from sentry_sdk.integrations import Integration 

15from sentry_sdk._compat import iteritems 

16 

17from sentry_sdk._types import MYPY 

18 

19if MYPY: 19 ↛ 20line 19 didn't jump to line 20, because the condition on line 19 was never true

20 from logging import LogRecord 

21 from typing import Any 

22 from typing import Dict 

23 from typing import Optional 

24 

25DEFAULT_LEVEL = logging.INFO 

26DEFAULT_EVENT_LEVEL = logging.ERROR 

27LOGGING_TO_EVENT_LEVEL = { 

28 logging.NOTSET: "notset", 

29 logging.DEBUG: "debug", 

30 logging.INFO: "info", 

31 logging.WARN: "warning", # WARN is same a WARNING 

32 logging.WARNING: "warning", 

33 logging.ERROR: "error", 

34 logging.FATAL: "fatal", 

35 logging.CRITICAL: "fatal", # CRITICAL is same as FATAL 

36} 

37 

38# Capturing events from those loggers causes recursion errors. We cannot allow 

39# the user to unconditionally create events from those loggers under any 

40# circumstances. 

41# 

42# Note: Ignoring by logger name here is better than mucking with thread-locals. 

43# We do not necessarily know whether thread-locals work 100% correctly in the user's environment. 

44_IGNORED_LOGGERS = set( 

45 ["sentry_sdk.errors", "urllib3.connectionpool", "urllib3.connection"] 

46) 

47 

48 

49def ignore_logger( 

50 name, # type: str 

51): 

52 # type: (...) -> None 

53 """This disables recording (both in breadcrumbs and as events) calls to 

54 a logger of a specific name. Among other uses, many of our integrations 

55 use this to prevent their actions being recorded as breadcrumbs. Exposed 

56 to users as a way to quiet spammy loggers. 

57 

58 :param name: The name of the logger to ignore (same string you would pass to ``logging.getLogger``). 

59 """ 

60 _IGNORED_LOGGERS.add(name) 

61 

62 

63class LoggingIntegration(Integration): 

64 identifier = "logging" 

65 

66 def __init__(self, level=DEFAULT_LEVEL, event_level=DEFAULT_EVENT_LEVEL): 

67 # type: (Optional[int], Optional[int]) -> None 

68 self._handler = None 

69 self._breadcrumb_handler = None 

70 

71 if level is not None: 71 ↛ 74line 71 didn't jump to line 74, because the condition on line 71 was never false

72 self._breadcrumb_handler = BreadcrumbHandler(level=level) 

73 

74 if event_level is not None: 74 ↛ exitline 74 didn't return from function '__init__', because the condition on line 74 was never false

75 self._handler = EventHandler(level=event_level) 

76 

77 def _handle_record(self, record): 

78 # type: (LogRecord) -> None 

79 if self._handler is not None and record.levelno >= self._handler.level: 

80 self._handler.handle(record) 

81 

82 if ( 

83 self._breadcrumb_handler is not None 

84 and record.levelno >= self._breadcrumb_handler.level 

85 ): 

86 self._breadcrumb_handler.handle(record) 

87 

88 @staticmethod 

89 def setup_once(): 

90 # type: () -> None 

91 old_callhandlers = logging.Logger.callHandlers 

92 

93 def sentry_patched_callhandlers(self, record): 

94 # type: (Any, LogRecord) -> Any 

95 try: 

96 return old_callhandlers(self, record) 

97 finally: 

98 # This check is done twice, once also here before we even get 

99 # the integration. Otherwise we have a high chance of getting 

100 # into a recursion error when the integration is resolved 

101 # (this also is slower). 

102 if record.name not in _IGNORED_LOGGERS: 102 ↛ 103line 102 didn't jump to line 103, because the condition on line 102 was never true

103 integration = Hub.current.get_integration(LoggingIntegration) 

104 if integration is not None: 

105 integration._handle_record(record) 

106 

107 logging.Logger.callHandlers = sentry_patched_callhandlers # type: ignore 

108 

109 

110def _can_record(record): 

111 # type: (LogRecord) -> bool 

112 """Prevents ignored loggers from recording""" 

113 for logger in _IGNORED_LOGGERS: 

114 if fnmatch(record.name, logger): 

115 return False 

116 return True 

117 

118 

119def _breadcrumb_from_record(record): 

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

121 return { 

122 "type": "log", 

123 "level": _logging_to_event_level(record), 

124 "category": record.name, 

125 "message": record.message, 

126 "timestamp": datetime.datetime.utcfromtimestamp(record.created), 

127 "data": _extra_from_record(record), 

128 } 

129 

130 

131def _logging_to_event_level(record): 

132 # type: (LogRecord) -> str 

133 return LOGGING_TO_EVENT_LEVEL.get( 

134 record.levelno, record.levelname.lower() if record.levelname else "" 

135 ) 

136 

137 

138COMMON_RECORD_ATTRS = frozenset( 

139 ( 

140 "args", 

141 "created", 

142 "exc_info", 

143 "exc_text", 

144 "filename", 

145 "funcName", 

146 "levelname", 

147 "levelno", 

148 "linenno", 

149 "lineno", 

150 "message", 

151 "module", 

152 "msecs", 

153 "msg", 

154 "name", 

155 "pathname", 

156 "process", 

157 "processName", 

158 "relativeCreated", 

159 "stack", 

160 "tags", 

161 "thread", 

162 "threadName", 

163 "stack_info", 

164 ) 

165) 

166 

167 

168def _extra_from_record(record): 

169 # type: (LogRecord) -> Dict[str, None] 

170 return { 

171 k: v 

172 for k, v in iteritems(vars(record)) 

173 if k not in COMMON_RECORD_ATTRS 

174 and (not isinstance(k, str) or not k.startswith("_")) 

175 } 

176 

177 

178class EventHandler(logging.Handler, object): 

179 """ 

180 A logging handler that emits Sentry events for each log record 

181 

182 Note that you do not have to use this class if the logging integration is enabled, which it is by default. 

183 """ 

184 

185 def emit(self, record): 

186 # type: (LogRecord) -> Any 

187 with capture_internal_exceptions(): 

188 self.format(record) 

189 return self._emit(record) 

190 

191 def _emit(self, record): 

192 # type: (LogRecord) -> None 

193 if not _can_record(record): 

194 return 

195 

196 hub = Hub.current 

197 if hub.client is None: 

198 return 

199 

200 client_options = hub.client.options 

201 

202 # exc_info might be None or (None, None, None) 

203 # 

204 # exc_info may also be any falsy value due to Python stdlib being 

205 # liberal with what it receives and Celery's billiard being "liberal" 

206 # with what it sends. See 

207 # https://github.com/getsentry/sentry-python/issues/904 

208 if record.exc_info and record.exc_info[0] is not None: 

209 event, hint = event_from_exception( 

210 record.exc_info, 

211 client_options=client_options, 

212 mechanism={"type": "logging", "handled": True}, 

213 ) 

214 elif record.exc_info and record.exc_info[0] is None: 

215 event = {} 

216 hint = {} 

217 with capture_internal_exceptions(): 

218 event["threads"] = { 

219 "values": [ 

220 { 

221 "stacktrace": current_stacktrace( 

222 client_options["with_locals"] 

223 ), 

224 "crashed": False, 

225 "current": True, 

226 } 

227 ] 

228 } 

229 else: 

230 event = {} 

231 hint = {} 

232 

233 hint["log_record"] = record 

234 

235 event["level"] = _logging_to_event_level(record) 

236 event["logger"] = record.name 

237 

238 # Log records from `warnings` module as separate issues 

239 record_caputured_from_warnings_module = ( 

240 record.name == "py.warnings" and record.msg == "%s" 

241 ) 

242 if record_caputured_from_warnings_module: 

243 # use the actual message and not "%s" as the message 

244 # this prevents grouping all warnings under one "%s" issue 

245 msg = record.args[0] # type: ignore 

246 

247 event["logentry"] = { 

248 "message": msg, 

249 "params": (), 

250 } 

251 

252 else: 

253 event["logentry"] = { 

254 "message": to_string(record.msg), 

255 "params": record.args, 

256 } 

257 

258 event["extra"] = _extra_from_record(record) 

259 

260 hub.capture_event(event, hint=hint) 

261 

262 

263# Legacy name 

264SentryHandler = EventHandler 

265 

266 

267class BreadcrumbHandler(logging.Handler, object): 

268 """ 

269 A logging handler that records breadcrumbs for each log record. 

270 

271 Note that you do not have to use this class if the logging integration is enabled, which it is by default. 

272 """ 

273 

274 def emit(self, record): 

275 # type: (LogRecord) -> Any 

276 with capture_internal_exceptions(): 

277 self.format(record) 

278 return self._emit(record) 

279 

280 def _emit(self, record): 

281 # type: (LogRecord) -> None 

282 if not _can_record(record): 

283 return 

284 

285 Hub.current.add_breadcrumb( 

286 _breadcrumb_from_record(record), hint={"log_record": record} 

287 )