"""Support for setting the level of logging for components.""" from __future__ import annotations import logging import re import voluptuous as vol from homeassistant.core import HomeAssistant, ServiceCall, callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType from . import websocket_api from .const import ( ATTR_LEVEL, DEFAULT_LOGSEVERITY, DOMAIN, LOGGER_DEFAULT, LOGGER_FILTERS, LOGGER_LOGS, LOGSEVERITY, SERVICE_SET_DEFAULT_LEVEL, SERVICE_SET_LEVEL, ) from .helpers import ( LoggerDomainConfig, LoggerSettings, set_default_log_level, set_log_levels, ) _VALID_LOG_LEVEL = vol.All(vol.Upper, vol.In(LOGSEVERITY), LOGSEVERITY.__getitem__) SERVICE_SET_DEFAULT_LEVEL_SCHEMA = vol.Schema({ATTR_LEVEL: _VALID_LOG_LEVEL}) SERVICE_SET_LEVEL_SCHEMA = vol.Schema({cv.string: _VALID_LOG_LEVEL}) CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { vol.Optional( LOGGER_DEFAULT, default=DEFAULT_LOGSEVERITY ): _VALID_LOG_LEVEL, vol.Optional(LOGGER_LOGS): vol.Schema({cv.string: _VALID_LOG_LEVEL}), vol.Optional(LOGGER_FILTERS): vol.Schema({cv.string: [cv.is_regex]}), } ) }, extra=vol.ALLOW_EXTRA, ) async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the logger component.""" settings = LoggerSettings(hass, config) domain_config = hass.data[DOMAIN] = LoggerDomainConfig({}, settings) logging.setLoggerClass(_get_logger_class(domain_config.overrides)) websocket_api.async_load_websocket_api(hass) await settings.async_load() # Set default log severity and filter logger_config = config.get(DOMAIN, {}) if LOGGER_DEFAULT in logger_config: set_default_log_level(hass, logger_config[LOGGER_DEFAULT]) if LOGGER_FILTERS in logger_config: log_filters: dict[str, list[re.Pattern]] = logger_config[LOGGER_FILTERS] for key, value in log_filters.items(): _add_log_filter(logging.getLogger(key), value) # Combine log levels configured in configuration.yaml with log levels set by frontend combined_logs = await settings.async_get_levels(hass) set_log_levels(hass, combined_logs) @callback def async_service_handler(service: ServiceCall) -> None: """Handle logger services.""" if service.service == SERVICE_SET_DEFAULT_LEVEL: set_default_log_level(hass, service.data[ATTR_LEVEL]) else: set_log_levels(hass, service.data) hass.services.async_register( DOMAIN, SERVICE_SET_DEFAULT_LEVEL, async_service_handler, schema=SERVICE_SET_DEFAULT_LEVEL_SCHEMA, ) hass.services.async_register( DOMAIN, SERVICE_SET_LEVEL, async_service_handler, schema=SERVICE_SET_LEVEL_SCHEMA, ) return True def _add_log_filter(logger: logging.Logger, patterns: list[re.Pattern]) -> None: """Add a Filter to the logger based on a regexp of the filter_str.""" def filter_func(logrecord: logging.LogRecord) -> bool: return not any(p.search(logrecord.getMessage()) for p in patterns) logger.addFilter(filter_func) def _get_logger_class(hass_overrides: dict[str, int]) -> type[logging.Logger]: """Create a logger subclass. logging.setLoggerClass checks if it is a subclass of Logger and so we cannot use partial to inject hass_overrides. """ class HassLogger(logging.Logger): """Home Assistant aware logger class.""" def setLevel(self, level: int | str) -> None: """Set the log level unless overridden.""" if self.name in hass_overrides: return super().setLevel(level) # pylint: disable=invalid-name def orig_setLevel(self, level: int | str) -> None: """Set the log level.""" super().setLevel(level) return HassLogger