Repository: getsentry/sentry-python

Issue:
Consider logging hierarchy for ignore_logger
Ref: https://docs.sentry.io/platforms/python/logging/#ignoring-a-logger

I'm trying to ignore a spammy module (in this case "ddtrace.writer"). While setting up my Sentry integration, I decided that I don't care about any error log from the entire module and so I called `ignore_logger("ddtrace")` instead of "ddtrace.writer".

This doesn't work (and looking at the implementation, it's clear why https://github.com/getsentry/sentry-python/blob/master/sentry_sdk/integrations/logging.py#L91-L93).

Since Python stdlib logging configuration works at a hierarchical level (eg. I can configure a logger with `qualname = ddtrace` and it'll apply to all its submodules), can the Sentry SDK be made to behave in a similar way?

Alternatives are either call ignore_logger for every single possible spammy source explicitly for all my dependencies, add sections to silence each module in my logging config, or write a custom before_send hook to filter. While these are all feasible, I'd still prefer to just call ignore_logger once per module as it's ultimately the simplest (IMO).


Discussion:
This makes a lot of sense. I'll think about it some more and whether we want to expand API surface to make an opt-in flag or change default behavior.
fwiw I believe we want to have an opt-in flag for this.
We can probably do glob support here

Resolving patch:
diff --git a/scripts/build_awslambda_layer.py b/scripts/build_awslambda_layer.py
--- a/scripts/build_awslambda_layer.py
+++ b/scripts/build_awslambda_layer.py
@@ -51,12 +51,14 @@ def create_init_serverless_sdk_package(self):
         Method that creates the init_serverless_sdk pkg in the
         sentry-python-serverless zip
         """
-        serverless_sdk_path = f'{self.packages_dir}/sentry_sdk/' \
-                              f'integrations/init_serverless_sdk'
+        serverless_sdk_path = (
+            f"{self.packages_dir}/sentry_sdk/" f"integrations/init_serverless_sdk"
+        )
         if not os.path.exists(serverless_sdk_path):
             os.makedirs(serverless_sdk_path)
-        shutil.copy('scripts/init_serverless_sdk.py',
-                    f'{serverless_sdk_path}/__init__.py')
+        shutil.copy(
+            "scripts/init_serverless_sdk.py", f"{serverless_sdk_path}/__init__.py"
+        )
 
     def zip(
         self, filename  # type: str
diff --git a/scripts/init_serverless_sdk.py b/scripts/init_serverless_sdk.py
--- a/scripts/init_serverless_sdk.py
+++ b/scripts/init_serverless_sdk.py
@@ -19,7 +19,7 @@
 sentry_sdk.init(
     dsn=os.environ["SENTRY_DSN"],
     integrations=[AwsLambdaIntegration(timeout_warning=True)],
-    traces_sample_rate=float(os.environ["SENTRY_TRACES_SAMPLE_RATE"])
+    traces_sample_rate=float(os.environ["SENTRY_TRACES_SAMPLE_RATE"]),
 )
 
 
diff --git a/sentry_sdk/integrations/logging.py b/sentry_sdk/integrations/logging.py
--- a/sentry_sdk/integrations/logging.py
+++ b/sentry_sdk/integrations/logging.py
@@ -2,6 +2,7 @@
 
 import logging
 import datetime
+from fnmatch import fnmatch
 
 from sentry_sdk.hub import Hub
 from sentry_sdk.utils import (
@@ -98,7 +99,11 @@ def sentry_patched_callhandlers(self, record):
 
 def _can_record(record):
     # type: (LogRecord) -> bool
-    return record.name not in _IGNORED_LOGGERS
+    """Prevents ignored loggers from recording"""
+    for logger in _IGNORED_LOGGERS:
+        if fnmatch(record.name, logger):
+            return False
+    return True
 
 
 def _breadcrumb_from_record(record):


Test patch:
diff --git a/tests/integrations/logging/test_logging.py b/tests/integrations/logging/test_logging.py
--- a/tests/integrations/logging/test_logging.py
+++ b/tests/integrations/logging/test_logging.py
@@ -3,7 +3,7 @@
 import pytest
 import logging
 
-from sentry_sdk.integrations.logging import LoggingIntegration
+from sentry_sdk.integrations.logging import LoggingIntegration, ignore_logger
 
 other_logger = logging.getLogger("testfoo")
 logger = logging.getLogger(__name__)
@@ -134,3 +134,30 @@ def filter(self, record):
 
     (event,) = events
     assert event["logentry"]["message"] == "hi"
+
+
+def test_ignore_logger(sentry_init, capture_events):
+    sentry_init(integrations=[LoggingIntegration()], default_integrations=False)
+    events = capture_events()
+
+    ignore_logger("testfoo")
+
+    other_logger.error("hi")
+
+    assert not events
+
+
+def test_ignore_logger_wildcard(sentry_init, capture_events):
+    sentry_init(integrations=[LoggingIntegration()], default_integrations=False)
+    events = capture_events()
+
+    ignore_logger("testfoo.*")
+
+    nested_logger = logging.getLogger("testfoo.submodule")
+
+    logger.error("hi")
+
+    nested_logger.error("bye")
+
+    (event,) = events
+    assert event["logentry"]["message"] == "hi"