Allow excluding modules from noisy logs check (#142020)

* Allow excluding modules from noisy logs check

* Cache non-excluded modules; hardcode self module name; optimize call

* Address review comments
This commit is contained in:
Abílio Costa 2025-04-02 14:07:00 +01:00 committed by Franck Nijhof
parent 2396fd1090
commit b38c647830
No known key found for this signature in database
GPG Key ID: D62583BA8AB11CA3
2 changed files with 56 additions and 4 deletions

View File

@ -29,16 +29,22 @@ class HomeAssistantQueueListener(logging.handlers.QueueListener):
LOG_COUNTS_RESET_INTERVAL = 300 LOG_COUNTS_RESET_INTERVAL = 300
MAX_LOGS_COUNT = 200 MAX_LOGS_COUNT = 200
EXCLUDED_LOG_COUNT_MODULES = [
"homeassistant.components.automation",
"homeassistant.components.script",
"homeassistant.setup",
"homeassistant.util.logging",
]
_last_reset: float _last_reset: float
_log_counts: dict[str, int] _log_counts: dict[str, int]
_warned_modules: set[str]
def __init__( def __init__(
self, queue: SimpleQueue[logging.Handler], *handlers: logging.Handler self, queue: SimpleQueue[logging.Handler], *handlers: logging.Handler
) -> None: ) -> None:
"""Initialize the handler.""" """Initialize the handler."""
super().__init__(queue, *handlers) super().__init__(queue, *handlers)
self._warned_modules = set() self._module_log_count_skip_flags: dict[str, bool] = {}
self._reset_counters(time.time()) self._reset_counters(time.time())
@override @override
@ -53,7 +59,11 @@ class HomeAssistantQueueListener(logging.handlers.QueueListener):
self._reset_counters(record.created) self._reset_counters(record.created)
module_name = record.name module_name = record.name
if module_name == __name__ or module_name in self._warned_modules:
if skip_flag := self._module_log_count_skip_flags.get(module_name):
return
if skip_flag is None and self._update_skip_flags(module_name):
return return
self._log_counts[module_name] += 1 self._log_counts[module_name] += 1
@ -66,13 +76,20 @@ class HomeAssistantQueueListener(logging.handlers.QueueListener):
module_name, module_name,
module_count, module_count,
) )
self._warned_modules.add(module_name) self._module_log_count_skip_flags[module_name] = True
def _reset_counters(self, time_sec: float) -> None: def _reset_counters(self, time_sec: float) -> None:
_LOGGER.debug("Resetting log counters") _LOGGER.debug("Resetting log counters")
self._last_reset = time_sec self._last_reset = time_sec
self._log_counts = defaultdict(int) self._log_counts = defaultdict(int)
def _update_skip_flags(self, module_name: str) -> bool:
excluded = any(
module_name.startswith(prefix) for prefix in self.EXCLUDED_LOG_COUNT_MODULES
)
self._module_log_count_skip_flags[module_name] = excluded
return excluded
class HomeAssistantQueueHandler(logging.handlers.QueueHandler): class HomeAssistantQueueHandler(logging.handlers.QueueHandler):
"""Process the log in another thread.""" """Process the log in another thread."""

View File

@ -160,6 +160,10 @@ async def test_catch_log_exception_catches_and_logs() -> None:
@patch("homeassistant.util.logging.HomeAssistantQueueListener.MAX_LOGS_COUNT", 5) @patch("homeassistant.util.logging.HomeAssistantQueueListener.MAX_LOGS_COUNT", 5)
@patch(
"homeassistant.util.logging.HomeAssistantQueueListener.EXCLUDED_LOG_COUNT_MODULES",
["excluded"],
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
( (
"logger1_count", "logger1_count",
@ -182,6 +186,7 @@ async def test_noisy_loggers(
logging_util.async_activate_log_queue_handler(hass) logging_util.async_activate_log_queue_handler(hass)
logger1 = logging.getLogger("noisy1") logger1 = logging.getLogger("noisy1")
logger2 = logging.getLogger("noisy2.module") logger2 = logging.getLogger("noisy2.module")
logger_excluded = logging.getLogger("excluded.module")
for _ in range(logger1_count): for _ in range(logger1_count):
logger1.info("This is a log") logger1.info("This is a log")
@ -189,6 +194,9 @@ async def test_noisy_loggers(
for _ in range(logger2_count): for _ in range(logger2_count):
logger2.info("This is another log") logger2.info("This is another log")
for _ in range(logging_util.HomeAssistantQueueListener.MAX_LOGS_COUNT + 1):
logger_excluded.info("This log should not trigger a warning")
await empty_log_queue() await empty_log_queue()
assert ( assert (
@ -203,6 +211,33 @@ async def test_noisy_loggers(
) )
== logger2_expected_notices == logger2_expected_notices
) )
# Ensure that the excluded module did not trigger a warning
assert (
caplog.text.count("is logging too frequently")
== logger1_expected_notices + logger2_expected_notices
)
# close the handler so the queue thread stops
logging.root.handlers[0].close()
@patch("homeassistant.util.logging.HomeAssistantQueueListener.MAX_LOGS_COUNT", 1)
async def test_noisy_loggers_ignores_self(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test that the noisy loggers warning does not trigger a warning for its own module."""
logging_util.async_activate_log_queue_handler(hass)
logger1 = logging.getLogger("noisy_module1")
logger2 = logging.getLogger("noisy_module2")
logger3 = logging.getLogger("noisy_module3")
logger1.info("This is a log")
logger2.info("This is a log")
logger3.info("This is a log")
await empty_log_queue()
assert caplog.text.count("logging too frequently") == 3
# close the handler so the queue thread stops # close the handler so the queue thread stops
logging.root.handlers[0].close() logging.root.handlers[0].close()