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
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1from __future__ import absolute_import
3import logging
4import datetime
5from fnmatch import fnmatch
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
17from sentry_sdk._types import MYPY
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
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}
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)
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.
58 :param name: The name of the logger to ignore (same string you would pass to ``logging.getLogger``).
59 """
60 _IGNORED_LOGGERS.add(name)
63class LoggingIntegration(Integration):
64 identifier = "logging"
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
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)
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)
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)
82 if (
83 self._breadcrumb_handler is not None
84 and record.levelno >= self._breadcrumb_handler.level
85 ):
86 self._breadcrumb_handler.handle(record)
88 @staticmethod
89 def setup_once():
90 # type: () -> None
91 old_callhandlers = logging.Logger.callHandlers
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)
107 logging.Logger.callHandlers = sentry_patched_callhandlers # type: ignore
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
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 }
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 )
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)
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 }
178class EventHandler(logging.Handler, object):
179 """
180 A logging handler that emits Sentry events for each log record
182 Note that you do not have to use this class if the logging integration is enabled, which it is by default.
183 """
185 def emit(self, record):
186 # type: (LogRecord) -> Any
187 with capture_internal_exceptions():
188 self.format(record)
189 return self._emit(record)
191 def _emit(self, record):
192 # type: (LogRecord) -> None
193 if not _can_record(record):
194 return
196 hub = Hub.current
197 if hub.client is None:
198 return
200 client_options = hub.client.options
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 = {}
233 hint["log_record"] = record
235 event["level"] = _logging_to_event_level(record)
236 event["logger"] = record.name
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
247 event["logentry"] = {
248 "message": msg,
249 "params": (),
250 }
252 else:
253 event["logentry"] = {
254 "message": to_string(record.msg),
255 "params": record.args,
256 }
258 event["extra"] = _extra_from_record(record)
260 hub.capture_event(event, hint=hint)
263# Legacy name
264SentryHandler = EventHandler
267class BreadcrumbHandler(logging.Handler, object):
268 """
269 A logging handler that records breadcrumbs for each log record.
271 Note that you do not have to use this class if the logging integration is enabled, which it is by default.
272 """
274 def emit(self, record):
275 # type: (LogRecord) -> Any
276 with capture_internal_exceptions():
277 self.format(record)
278 return self._emit(record)
280 def _emit(self, record):
281 # type: (LogRecord) -> None
282 if not _can_record(record):
283 return
285 Hub.current.add_breadcrumb(
286 _breadcrumb_from_record(record), hint={"log_record": record}
287 )