diff --git a/homeassistant/components/stream/__init__.py b/homeassistant/components/stream/__init__.py index 691ba262ee2371..626a03b785f4be 100644 --- a/homeassistant/components/stream/__init__.py +++ b/homeassistant/components/stream/__init__.py @@ -29,6 +29,7 @@ import voluptuous as vol from yarl import URL +from homeassistant.components.logger import EVENT_LOGGING_CHANGED from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import Event, HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -188,36 +189,32 @@ def convert_stream_options( ) -def filter_libav_logging() -> None: - """Filter libav logging to only log when the stream logger is at DEBUG.""" +@callback +def update_pyav_logging(_event: Event | None = None) -> None: + """Adjust libav logging to only log when the stream logger is at DEBUG.""" - def libav_filter(record: logging.LogRecord) -> bool: - return logging.getLogger(__name__).isEnabledFor(logging.DEBUG) + def set_pyav_logging(enable: bool) -> None: + """Turn PyAV logging on or off.""" + import av # pylint: disable=import-outside-toplevel - for logging_namespace in ( - "libav.NULL", - "libav.h264", - "libav.hevc", - "libav.hls", - "libav.mp4", - "libav.mpegts", - "libav.rtsp", - "libav.tcp", - "libav.tls", - ): - logging.getLogger(logging_namespace).addFilter(libav_filter) + av.logging.set_level(av.logging.VERBOSE if enable else av.logging.FATAL) - # Set log level to error for libav.mp4 - logging.getLogger("libav.mp4").setLevel(logging.ERROR) - # Suppress "deprecated pixel format" WARNING - logging.getLogger("libav.swscaler").setLevel(logging.ERROR) + # enable PyAV logging iff Stream logger is set to debug + set_pyav_logging(logging.getLogger(__name__).isEnabledFor(logging.DEBUG)) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up stream.""" - # Drop libav log messages if stream logging is above DEBUG - filter_libav_logging() + # Only pass through PyAV log messages if stream logging is above DEBUG + cancel_logging_listener = hass.bus.async_listen( + EVENT_LOGGING_CHANGED, update_pyav_logging + ) + # libav.mp4 and libav.swscaler have a few unimportant messages that are logged + # at logging.WARNING. Set those Logger levels to logging.ERROR + for logging_namespace in ("libav.mp4", "libav.swscaler"): + logging.getLogger(logging_namespace).setLevel(logging.ERROR) + update_pyav_logging() # Keep import here so that we can import stream integration without installing reqs # pylint: disable-next=import-outside-toplevel @@ -258,6 +255,7 @@ async def shutdown(event: Event) -> None: ]: await asyncio.wait(awaitables) _LOGGER.debug("Stopped stream workers") + cancel_logging_listener() hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown) diff --git a/homeassistant/components/stream/manifest.json b/homeassistant/components/stream/manifest.json index 96474ceb7eb7b9..47a4ddd0653be6 100644 --- a/homeassistant/components/stream/manifest.json +++ b/homeassistant/components/stream/manifest.json @@ -2,7 +2,7 @@ "domain": "stream", "name": "Stream", "codeowners": ["@hunterjm", "@uvjustin", "@allenporter"], - "dependencies": ["http"], + "dependencies": ["http", "logger"], "documentation": "https://www.home-assistant.io/integrations/stream", "integration_type": "system", "iot_class": "local_push", diff --git a/pyproject.toml b/pyproject.toml index e62bdbf3e30e4b..73f47998ea7943 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,6 +109,7 @@ load-plugins = [ persistent = false extension-pkg-allow-list = [ "av.audio.stream", + "av.logging", "av.stream", "ciso8601", "orjson", diff --git a/tests/components/stream/test_init.py b/tests/components/stream/test_init.py index 0c625a8dec167c..525eb9d859d59b 100644 --- a/tests/components/stream/test_init.py +++ b/tests/components/stream/test_init.py @@ -4,6 +4,7 @@ import av import pytest +from homeassistant.components.logger import EVENT_LOGGING_CHANGED from homeassistant.components.stream import __name__ as stream_name from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component @@ -14,8 +15,6 @@ async def test_log_levels( ) -> None: """Test that the worker logs the url without username and password.""" - logging.getLogger(stream_name).setLevel(logging.INFO) - await async_setup_component(hass, "stream", {"stream": {}}) # These namespaces should only pass log messages when the stream logger @@ -31,11 +30,17 @@ async def test_log_levels( "NULL", ) + logging.getLogger(stream_name).setLevel(logging.INFO) + hass.bus.async_fire(EVENT_LOGGING_CHANGED) + await hass.async_block_till_done() + # Since logging is at INFO, these should not pass for namespace in namespaces_to_toggle: av.logging.log(av.logging.ERROR, namespace, "SHOULD NOT PASS") logging.getLogger(stream_name).setLevel(logging.DEBUG) + hass.bus.async_fire(EVENT_LOGGING_CHANGED) + await hass.async_block_till_done() # Since logging is now at DEBUG, these should now pass for namespace in namespaces_to_toggle: