Skip to content

Commit

Permalink
feat: allow ignoring loggers via optional argument
Browse files Browse the repository at this point in the history
  • Loading branch information
chiragjn authored and aexvir committed Mar 2, 2021
1 parent 189a78e commit 576af0f
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 11 deletions.
46 changes: 35 additions & 11 deletions structlog_sentry/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import logging
import sys
from typing import List, Optional, Tuple, Union
from typing import List, Optional, Tuple, Union, Set, Iterable

from sentry_sdk import capture_event
from sentry_sdk.integrations.logging import ignore_logger
from sentry_sdk.integrations.logging import ignore_logger as logging_int_ignore_logger
from sentry_sdk.utils import event_from_exception


Expand All @@ -19,6 +19,7 @@ def __init__(
active: bool = True,
as_extra: bool = True,
tag_keys: Union[List[str], str] = None,
ignore_loggers: Optional[Iterable[str]] = None,
) -> None:
"""
:param level: events of this or higher levels will be reported to Sentry.
Expand All @@ -27,12 +28,37 @@ def __init__(
:param tag_keys: a list of keys. If any if these keys appear in `event_dict`,
the key and its corresponding value in `event_dict` will be used as Sentry
event tags. use `"__all__"` to report all key/value pairs of event as tags.
:param ignore_loggers: a list of logger names to ignore any events from.
"""
self.level = level
self.active = active
self.tag_keys = tag_keys
self._as_extra = as_extra
self._original_event_dict = None
self._ignored_loggers: Set[str] = set()
if ignore_loggers is not None:
self._ignored_loggers.update(set(ignore_loggers))

@staticmethod
def _get_logger_name(logger, event_dict: dict) -> Optional[str]:
"""Get logger name from event_dict with a fallbacks to logger.name and record.name
:param logger: logger instance
:param event_dict: structlog event_dict
"""
record = event_dict.get("_record")
l_name = event_dict.get("logger")
logger_name = None

if l_name:
logger_name = l_name
elif record:
logger_name = record.name

if not logger_name and logger:
logger_name = logger.name

return logger_name

def _get_event_and_hint(self, event_dict: dict) -> Tuple[dict, Optional[str]]:
"""Create a sentry event and hint from structlog `event_dict` and sys.exc_info.
Expand Down Expand Up @@ -76,6 +102,11 @@ def _log(self, event_dict: dict) -> str:

def __call__(self, logger, method, event_dict) -> dict:
"""A middleware to process structlog `event_dict` and send it to Sentry."""
logger_name = self._get_logger_name(logger=logger, event_dict=event_dict)
if logger_name in self._ignored_loggers:
event_dict["sentry"] = "ignored"
return event_dict

self._original_event_dict = event_dict.copy()
sentry_skip = event_dict.pop("sentry_skip", False)
do_log = getattr(logging, event_dict["level"].upper()) >= self.level
Expand Down Expand Up @@ -117,18 +148,11 @@ def _ignore_logger(self, logger, event_dict: dict) -> None:
:param logger: logger instance
:param event_dict: structlog event_dict
"""
record = event_dict.get("_record")
l_name = event_dict.get("logger")
if l_name:
logger_name = l_name
elif record is None:
logger_name = logger.name
else:
logger_name = record.name
logger_name = self._get_logger_name(logger=logger, event_dict=event_dict)

if not logger_name:
raise Exception("Cannot ignore logger without a name.")

if logger_name not in self._ignored:
ignore_logger(logger_name)
logging_int_ignore_logger(logger_name)
self._ignored.add(logger_name)
61 changes: 61 additions & 0 deletions test/test_sentry_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,67 @@ def test_sentry_log_specific_keys_as_tags(mocker, level):
assert event_dict.get("sentry") != "sent"


def test_sentry_get_logger_name():
event_data = {
"level": "info",
"event": "message",
"logger": "EventLogger",
"_record": MockLogger("RecordLogger"),
}
assert (
SentryProcessor._get_logger_name(logger=None, event_dict=event_data)
== "EventLogger"
)

event_data = {
"level": "info",
"event": "message",
"_record": MockLogger("RecordLogger"),
}
assert (
SentryProcessor._get_logger_name(logger=None, event_dict=event_data)
== "RecordLogger"
)

event_data = {
"level": "info",
"event": "message",
}
assert (
SentryProcessor._get_logger_name(
logger=MockLogger("EventLogger"), event_dict=event_data
)
== "EventLogger"
)


@pytest.mark.parametrize("level", ["debug", "info", "warning", "error", "critical"])
def test_sentry_ignore_logger(mocker, level):
m_capture_event = mocker.patch("structlog_sentry.capture_event")
blacklisted_logger = MockLogger("test.blacklisted")
whitelisted_logger = MockLogger("test.whitelisted")
processor = SentryProcessor(
level=getattr(logging, level.upper()), ignore_loggers=["test.blacklisted"]
)

event_data = {"level": level, "event": level + " message"}
sentry_event_data = event_data.copy()

blacklisted_logger_event_dict = processor(
blacklisted_logger, None, event_data.copy()
)
whitelisted_logger_event_dict = processor(
whitelisted_logger, None, event_data.copy()
)

m_capture_event.assert_called_once_with(
{"level": level, "message": event_data["event"], "extra": sentry_event_data,},
hint=None,
)
assert blacklisted_logger_event_dict.get("sentry") == "ignored"
assert whitelisted_logger_event_dict.get("sentry") != "ignored"


def test_sentry_json_ignore_logger_using_event_dict_logger_name(mocker):
m_ignore_logger = mocker.patch("structlog_sentry.ignore_logger")
m_logger = MockLogger("MockLogger")
Expand Down

0 comments on commit 576af0f

Please sign in to comment.