Add websocket endpoints to control integration logging (#65158)

Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Erik <erik@montnemery.com>
This commit is contained in:
J. Nick Koston 2022-11-17 08:57:43 -06:00 committed by GitHub
parent 9d607c8bd5
commit 8792d664e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 784 additions and 69 deletions

View File

@ -174,6 +174,7 @@ homeassistant.components.litterrobot.*
homeassistant.components.local_ip.*
homeassistant.components.lock.*
homeassistant.components.logbook.*
homeassistant.components.logger.*
homeassistant.components.lookin.*
homeassistant.components.luftdaten.*
homeassistant.components.mailbox.*

View File

@ -1,5 +1,8 @@
"""Support for setting the level of logging for components."""
from __future__ import annotations
import logging
import re
import voluptuous as vol
@ -7,29 +10,26 @@ from homeassistant.core import HomeAssistant, ServiceCall, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import ConfigType
DOMAIN = "logger"
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,
)
SERVICE_SET_DEFAULT_LEVEL = "set_default_level"
SERVICE_SET_LEVEL = "set_level"
LOGSEVERITY = {
"CRITICAL": 50,
"FATAL": 50,
"ERROR": 40,
"WARNING": 30,
"WARN": 30,
"INFO": 20,
"DEBUG": 10,
"NOTSET": 0,
}
LOGGER_DEFAULT = "default"
LOGGER_LOGS = "logs"
LOGGER_FILTERS = "filters"
ATTR_LEVEL = "level"
_VALID_LOG_LEVEL = vol.All(vol.Upper, vol.In(LOGSEVERITY))
_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})
@ -38,7 +38,9 @@ CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Optional(LOGGER_DEFAULT): _VALID_LOG_LEVEL,
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]}),
}
@ -50,42 +52,38 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the logger component."""
hass.data[DOMAIN] = {}
logging.setLoggerClass(_get_logger_class(hass.data[DOMAIN]))
@callback
def set_default_log_level(level):
"""Set the default log level for components."""
_set_log_level(logging.getLogger(""), level)
settings = LoggerSettings(hass, config)
@callback
def set_log_levels(logpoints):
"""Set the specified log levels."""
hass.data[DOMAIN].update(logpoints)
for key, value in logpoints.items():
_set_log_level(logging.getLogger(key), value)
domain_config = hass.data[DOMAIN] = LoggerDomainConfig({}, settings)
logging.setLoggerClass(_get_logger_class(domain_config.overrides))
# Set default log severity
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(logger_config[LOGGER_DEFAULT])
if LOGGER_LOGS in logger_config:
set_log_levels(config[DOMAIN][LOGGER_LOGS])
set_default_log_level(hass, logger_config[LOGGER_DEFAULT])
if LOGGER_FILTERS in logger_config:
for key, value in logger_config[LOGGER_FILTERS].items():
logger = logging.getLogger(key)
_add_log_filter(logger, value)
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(service.data.get(ATTR_LEVEL))
set_default_log_level(hass, service.data[ATTR_LEVEL])
else:
set_log_levels(service.data)
set_log_levels(hass, service.data)
hass.services.async_register(
DOMAIN,
@ -104,24 +102,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
def _set_log_level(logger, level):
"""Set the log level.
Any logger fetched before this integration is loaded will use old class.
"""
getattr(logger, "orig_setLevel", logger.setLevel)(LOGSEVERITY[level])
def _add_log_filter(logger, patterns):
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):
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):
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
@ -131,7 +121,7 @@ def _get_logger_class(hass_overrides):
class HassLogger(logging.Logger):
"""Home Assistant aware logger class."""
def setLevel(self, level) -> None:
def setLevel(self, level: int | str) -> None:
"""Set the log level unless overridden."""
if self.name in hass_overrides:
return
@ -139,7 +129,7 @@ def _get_logger_class(hass_overrides):
super().setLevel(level)
# pylint: disable=invalid-name
def orig_setLevel(self, level) -> None:
def orig_setLevel(self, level: int | str) -> None:
"""Set the log level."""
super().setLevel(level)

View File

@ -0,0 +1,42 @@
"""Constants for the Logger integration."""
import logging
DOMAIN = "logger"
SERVICE_SET_DEFAULT_LEVEL = "set_default_level"
SERVICE_SET_LEVEL = "set_level"
LOGSEVERITY_NOTSET = "NOTSET"
LOGSEVERITY_DEBUG = "DEBUG"
LOGSEVERITY_INFO = "INFO"
LOGSEVERITY_WARNING = "WARNING"
LOGSEVERITY_ERROR = "ERROR"
LOGSEVERITY_CRITICAL = "CRITICAL"
LOGSEVERITY_WARN = "WARN"
LOGSEVERITY_FATAL = "FATAL"
LOGSEVERITY = {
LOGSEVERITY_CRITICAL: logging.CRITICAL,
LOGSEVERITY_FATAL: logging.FATAL,
LOGSEVERITY_ERROR: logging.ERROR,
LOGSEVERITY_WARNING: logging.WARNING,
LOGSEVERITY_WARN: logging.WARN,
LOGSEVERITY_INFO: logging.INFO,
LOGSEVERITY_DEBUG: logging.DEBUG,
LOGSEVERITY_NOTSET: logging.NOTSET,
}
DEFAULT_LOGSEVERITY = "DEBUG"
LOGGER_DEFAULT = "default"
LOGGER_LOGS = "logs"
LOGGER_FILTERS = "filters"
ATTR_LEVEL = "level"
EVENT_LOGGING_CHANGED = "logging_changed"
STORAGE_KEY = "core.logger"
STORAGE_LOG_KEY = "logs"
STORAGE_VERSION = 1

View File

@ -0,0 +1,217 @@
"""Helpers for the logger integration."""
from __future__ import annotations
from collections import defaultdict
from collections.abc import Mapping
import contextlib
from dataclasses import asdict, dataclass
import logging
from typing import Any, cast
from homeassistant.backports.enum import StrEnum
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.storage import Store
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import IntegrationNotFound, async_get_integration
from .const import (
DOMAIN,
EVENT_LOGGING_CHANGED,
LOGGER_DEFAULT,
LOGGER_LOGS,
LOGSEVERITY,
LOGSEVERITY_NOTSET,
STORAGE_KEY,
STORAGE_LOG_KEY,
STORAGE_VERSION,
)
@callback
def async_get_domain_config(hass: HomeAssistant) -> LoggerDomainConfig:
"""Return the domain config."""
return cast(LoggerDomainConfig, hass.data[DOMAIN])
@callback
def set_default_log_level(hass: HomeAssistant, level: int) -> None:
"""Set the default log level for components."""
_set_log_level(logging.getLogger(""), level)
hass.bus.async_fire(EVENT_LOGGING_CHANGED)
@callback
def set_log_levels(hass: HomeAssistant, logpoints: Mapping[str, int]) -> None:
"""Set the specified log levels."""
async_get_domain_config(hass).overrides.update(logpoints)
for key, value in logpoints.items():
_set_log_level(logging.getLogger(key), value)
hass.bus.async_fire(EVENT_LOGGING_CHANGED)
def _set_log_level(logger: logging.Logger, level: int) -> None:
"""Set the log level.
Any logger fetched before this integration is loaded will use old class.
"""
getattr(logger, "orig_setLevel", logger.setLevel)(level)
def _chattiest_log_level(level1: int, level2: int) -> int:
"""Return the chattiest log level."""
if level1 == logging.NOTSET:
return level2
if level2 == logging.NOTSET:
return level1
return min(level1, level2)
async def get_integration_loggers(hass: HomeAssistant, domain: str) -> list[str]:
"""Get loggers for an integration."""
loggers = [f"homeassistant.components.{domain}"]
with contextlib.suppress(IntegrationNotFound):
integration = await async_get_integration(hass, domain)
if integration.loggers:
loggers.extend(integration.loggers)
return loggers
@dataclass
class LoggerSetting:
"""Settings for a single module or integration."""
level: str
persistence: str
type: str
@dataclass
class LoggerDomainConfig:
"""Logger domain config."""
overrides: dict[str, Any]
settings: LoggerSettings
class LogPersistance(StrEnum):
"""Log persistence."""
NONE = "none"
ONCE = "once"
PERMANENT = "permanent"
class LogSettingsType(StrEnum):
"""Log settings type."""
INTEGRATION = "integration"
MODULE = "module"
class LoggerSettings:
"""Manage log settings."""
_stored_config: dict[str, dict[str, LoggerSetting]]
def __init__(self, hass: HomeAssistant, yaml_config: ConfigType) -> None:
"""Initialize log settings."""
self._yaml_config = yaml_config
self._default_level = logging.INFO
if DOMAIN in yaml_config:
self._default_level = yaml_config[DOMAIN][LOGGER_DEFAULT]
self._store: Store[dict[str, dict[str, dict[str, Any]]]] = Store(
hass, STORAGE_VERSION, STORAGE_KEY
)
async def async_load(self) -> None:
"""Load stored settings."""
stored_config = await self._store.async_load()
if not stored_config:
self._stored_config = {STORAGE_LOG_KEY: {}}
return
def reset_persistence(settings: LoggerSetting) -> LoggerSetting:
"""Reset persistence."""
if settings.persistence == LogPersistance.ONCE:
settings.persistence = LogPersistance.NONE
return settings
stored_log_config = stored_config[STORAGE_LOG_KEY]
# Reset domains for which the overrides should only be applied once
self._stored_config = {
STORAGE_LOG_KEY: {
domain: reset_persistence(LoggerSetting(**settings))
for domain, settings in stored_log_config.items()
}
}
await self._store.async_save(self._async_data_to_save())
@callback
def _async_data_to_save(self) -> dict[str, dict[str, dict[str, str]]]:
"""Generate data to be saved."""
stored_log_config = self._stored_config[STORAGE_LOG_KEY]
return {
STORAGE_LOG_KEY: {
domain: asdict(settings)
for domain, settings in stored_log_config.items()
if settings.persistence
in (LogPersistance.ONCE, LogPersistance.PERMANENT)
}
}
@callback
def async_save(self) -> None:
"""Save settings."""
self._store.async_delay_save(self._async_data_to_save, 15)
@callback
def _async_get_logger_logs(self) -> dict[str, int]:
"""Get the logger logs."""
logger_logs: dict[str, int] = self._yaml_config.get(DOMAIN, {}).get(
LOGGER_LOGS, {}
)
return logger_logs
async def async_update(
self, hass: HomeAssistant, domain: str, settings: LoggerSetting
) -> None:
"""Update settings."""
stored_log_config = self._stored_config[STORAGE_LOG_KEY]
if settings.level == LOGSEVERITY_NOTSET:
stored_log_config.pop(domain, None)
else:
stored_log_config[domain] = settings
self.async_save()
if settings.type == LogSettingsType.INTEGRATION:
loggers = await get_integration_loggers(hass, domain)
else:
loggers = [domain]
combined_logs = {logger: LOGSEVERITY[settings.level] for logger in loggers}
# Don't override the log levels with the ones from YAML
# since we want whatever the user is asking for to be honored.
set_log_levels(hass, combined_logs)
async def async_get_levels(self, hass: HomeAssistant) -> dict[str, int]:
"""Get combination of levels from yaml and storage."""
combined_logs = defaultdict(lambda: logging.CRITICAL)
for domain, settings in self._stored_config[STORAGE_LOG_KEY].items():
if settings.type == LogSettingsType.INTEGRATION:
loggers = await get_integration_loggers(hass, domain)
else:
loggers = [domain]
for logger in loggers:
combined_logs[logger] = LOGSEVERITY[settings.level]
if yaml_log_settings := self._async_get_logger_logs():
for domain, level in yaml_log_settings.items():
combined_logs[domain] = _chattiest_log_level(
combined_logs[domain], level
)
return dict(combined_logs)

View File

@ -0,0 +1,104 @@
"""Websocket API handlers for the logger integration."""
import logging
from typing import Any
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.components.websocket_api.connection import ActiveConnection
from homeassistant.core import HomeAssistant, callback
from homeassistant.loader import IntegrationNotFound, async_get_integration
from homeassistant.setup import async_get_loaded_integrations
from .const import LOGSEVERITY
from .helpers import (
LoggerSetting,
LogPersistance,
LogSettingsType,
async_get_domain_config,
)
@callback
def async_load_websocket_api(hass: HomeAssistant) -> None:
"""Set up the websocket API."""
websocket_api.async_register_command(hass, handle_integration_log_info)
websocket_api.async_register_command(hass, handle_integration_log_level)
websocket_api.async_register_command(hass, handle_module_log_level)
@websocket_api.websocket_command({vol.Required("type"): "logger/log_info"})
@websocket_api.async_response
async def handle_integration_log_info(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
) -> None:
"""Handle integrations logger info."""
connection.send_result(
msg["id"],
[
{
"domain": integration,
"level": logging.getLogger(
f"homeassistant.components.{integration}"
).getEffectiveLevel(),
}
for integration in async_get_loaded_integrations(hass)
],
)
@websocket_api.websocket_command(
{
vol.Required("type"): "logger/integration_log_level",
vol.Required("integration"): str,
vol.Required("level"): vol.In(LOGSEVERITY),
vol.Required("persistence"): vol.Coerce(LogPersistance),
}
)
@websocket_api.async_response
async def handle_integration_log_level(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
) -> None:
"""Handle setting integration log level."""
try:
await async_get_integration(hass, msg["integration"])
except IntegrationNotFound:
connection.send_error(
msg["id"], websocket_api.const.ERR_NOT_FOUND, "Integration not found"
)
return
await async_get_domain_config(hass).settings.async_update(
hass,
msg["integration"],
LoggerSetting(
level=msg["level"],
persistence=msg["persistence"],
type=LogSettingsType.INTEGRATION,
),
)
connection.send_message(websocket_api.messages.result_message(msg["id"]))
@websocket_api.websocket_command(
{
vol.Required("type"): "logger/log_level",
vol.Required("module"): str,
vol.Required("level"): vol.In(LOGSEVERITY),
vol.Required("persistence"): vol.Coerce(LogPersistance),
}
)
@websocket_api.async_response
async def handle_module_log_level(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
) -> None:
"""Handle setting integration log level."""
await async_get_domain_config(hass).settings.async_update(
hass,
msg["module"],
LoggerSetting(
level=msg["level"],
persistence=msg["persistence"],
type=LogSettingsType.MODULE,
),
)
connection.send_message(websocket_api.messages.result_message(msg["id"]))

View File

@ -1493,6 +1493,16 @@ disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.logger.*]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.lookin.*]
check_untyped_defs = true
disallow_incomplete_defs = true

View File

@ -0,0 +1,12 @@
"""Test fixtures for the Logger component."""
import logging
import pytest
@pytest.fixture(autouse=True)
def restore_logging_class():
"""Restore logging class."""
klass = logging.getLoggerClass()
yield
logging.setLoggerClass(klass)

View File

@ -3,8 +3,6 @@ from collections import defaultdict
import logging
from unittest.mock import Mock, patch
import pytest
from homeassistant.components import logger
from homeassistant.components.logger import LOGSEVERITY
from homeassistant.setup import async_setup_component
@ -15,14 +13,8 @@ ZONE_NS = f"{COMPONENTS_NS}.zone"
GROUP_NS = f"{COMPONENTS_NS}.group"
CONFIGED_NS = "otherlibx"
UNCONFIG_NS = "unconfigurednamespace"
@pytest.fixture(autouse=True)
def restore_logging_class():
"""Restore logging class."""
klass = logging.getLoggerClass()
yield
logging.setLoggerClass(klass)
INTEGRATION = "test_component"
INTEGRATION_NS = f"homeassistant.components.{INTEGRATION}"
async def test_log_filtering(hass, caplog):
@ -158,7 +150,7 @@ async def test_setting_level(hass):
)
async def test_can_set_level(hass):
async def test_can_set_level_from_yaml(hass):
"""Test logger propagation."""
assert await async_setup_component(
@ -178,7 +170,49 @@ async def test_can_set_level(hass):
}
},
)
await _assert_log_levels(hass)
_reset_logging()
async def test_can_set_level_from_store(hass, hass_storage):
"""Test setting up logs from store."""
hass_storage["core.logger"] = {
"data": {
"logs": {
CONFIGED_NS: {
"level": "WARNING",
"persistence": "once",
"type": "module",
},
f"{CONFIGED_NS}.info": {
"level": "INFO",
"persistence": "once",
"type": "module",
},
f"{CONFIGED_NS}.debug": {
"level": "DEBUG",
"persistence": "once",
"type": "module",
},
HASS_NS: {"level": "WARNING", "persistence": "once", "type": "module"},
COMPONENTS_NS: {
"level": "INFO",
"persistence": "once",
"type": "module",
},
ZONE_NS: {"level": "DEBUG", "persistence": "once", "type": "module"},
GROUP_NS: {"level": "INFO", "persistence": "once", "type": "module"},
}
},
"key": "core.logger",
"version": 1,
}
assert await async_setup_component(hass, "logger", {})
await _assert_log_levels(hass)
_reset_logging()
async def _assert_log_levels(hass):
assert logging.getLogger(UNCONFIG_NS).level == logging.NOTSET
assert logging.getLogger(UNCONFIG_NS).isEnabledFor(logging.CRITICAL) is True
assert (
@ -255,3 +289,113 @@ async def test_can_set_level(hass):
assert logging.getLogger(CONFIGED_NS).level == logging.WARNING
logging.getLogger("").setLevel(logging.NOTSET)
def _reset_logging():
"""Reset loggers."""
logging.getLogger(CONFIGED_NS).orig_setLevel(logging.NOTSET)
logging.getLogger(f"{CONFIGED_NS}.info").orig_setLevel(logging.NOTSET)
logging.getLogger(f"{CONFIGED_NS}.debug").orig_setLevel(logging.NOTSET)
logging.getLogger(HASS_NS).orig_setLevel(logging.NOTSET)
logging.getLogger(COMPONENTS_NS).orig_setLevel(logging.NOTSET)
logging.getLogger(ZONE_NS).orig_setLevel(logging.NOTSET)
logging.getLogger(GROUP_NS).orig_setLevel(logging.NOTSET)
logging.getLogger(INTEGRATION_NS).orig_setLevel(logging.NOTSET)
async def test_can_set_integration_level_from_store(hass, hass_storage):
"""Test setting up integration logs from store."""
hass_storage["core.logger"] = {
"data": {
"logs": {
INTEGRATION: {
"level": "WARNING",
"persistence": "once",
"type": "integration",
},
}
},
"key": "core.logger",
"version": 1,
}
assert await async_setup_component(hass, "logger", {})
assert logging.getLogger(INTEGRATION_NS).isEnabledFor(logging.DEBUG) is False
assert logging.getLogger(INTEGRATION_NS).isEnabledFor(logging.WARNING) is True
_reset_logging()
async def test_chattier_log_level_wins_1(hass, hass_storage):
"""Test chattier log level in store takes precedence."""
hass_storage["core.logger"] = {
"data": {
"logs": {
INTEGRATION_NS: {
"level": "DEBUG",
"persistence": "once",
"type": "module",
},
}
},
"key": "core.logger",
"version": 1,
}
assert await async_setup_component(
hass,
"logger",
{
"logger": {
"logs": {
INTEGRATION_NS: "warning",
}
}
},
)
assert logging.getLogger(INTEGRATION_NS).isEnabledFor(logging.DEBUG) is True
assert logging.getLogger(INTEGRATION_NS).isEnabledFor(logging.WARNING) is True
_reset_logging()
async def test_chattier_log_level_wins_2(hass, hass_storage):
"""Test chattier log level in yaml takes precedence."""
hass_storage["core.logger"] = {
"data": {
"logs": {
INTEGRATION_NS: {
"level": "WARNING",
"persistence": "once",
"type": "module",
},
}
},
"key": "core.logger",
"version": 1,
}
assert await async_setup_component(
hass, "logger", {"logger": {"logs": {INTEGRATION_NS: "debug"}}}
)
assert logging.getLogger(INTEGRATION_NS).isEnabledFor(logging.DEBUG) is True
assert logging.getLogger(INTEGRATION_NS).isEnabledFor(logging.WARNING) is True
_reset_logging()
async def test_log_once_removed_from_store(hass, hass_storage):
"""Test logs with persistence "once" are removed from the store at startup."""
hass_storage["core.logger"] = {
"data": {
"logs": {
ZONE_NS: {"type": "module", "level": "DEBUG", "persistence": "once"}
}
},
"key": "core.logger",
"version": 1,
}
assert await async_setup_component(hass, "logger", {})
assert hass_storage["core.logger"]["data"] == {"logs": {}}

View File

@ -0,0 +1,195 @@
"""Tests for Logger Websocket API commands."""
import logging
from homeassistant.components.logger.helpers import async_get_domain_config
from homeassistant.components.websocket_api import const
from homeassistant.setup import async_setup_component
async def test_integration_log_info(hass, hass_ws_client, hass_admin_user):
"""Test fetching integration log info."""
assert await async_setup_component(hass, "logger", {})
logging.getLogger("homeassistant.components.http").setLevel(logging.DEBUG)
logging.getLogger("homeassistant.components.websocket_api").setLevel(logging.DEBUG)
websocket_client = await hass_ws_client()
await websocket_client.send_json({"id": 7, "type": "logger/log_info"})
msg = await websocket_client.receive_json()
assert msg["id"] == 7
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
assert {"domain": "http", "level": logging.DEBUG} in msg["result"]
assert {"domain": "websocket_api", "level": logging.DEBUG} in msg["result"]
async def test_integration_log_level_logger_not_loaded(
hass, hass_ws_client, hass_admin_user
):
"""Test setting integration log level."""
websocket_client = await hass_ws_client()
await websocket_client.send_json(
{
"id": 7,
"type": "logger/log_level",
"integration": "websocket_api",
"level": logging.DEBUG,
"persistence": "none",
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 7
assert msg["type"] == const.TYPE_RESULT
assert not msg["success"]
async def test_integration_log_level(hass, hass_ws_client, hass_admin_user):
"""Test setting integration log level."""
websocket_client = await hass_ws_client()
assert await async_setup_component(hass, "logger", {})
await websocket_client.send_json(
{
"id": 7,
"type": "logger/integration_log_level",
"integration": "websocket_api",
"level": "DEBUG",
"persistence": "none",
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 7
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
assert async_get_domain_config(hass).overrides == {
"homeassistant.components.websocket_api": logging.DEBUG
}
async def test_integration_log_level_unknown_integration(
hass, hass_ws_client, hass_admin_user
):
"""Test setting integration log level for an unknown integration."""
websocket_client = await hass_ws_client()
assert await async_setup_component(hass, "logger", {})
await websocket_client.send_json(
{
"id": 7,
"type": "logger/integration_log_level",
"integration": "websocket_api_123",
"level": "DEBUG",
"persistence": "none",
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 7
assert msg["type"] == const.TYPE_RESULT
assert not msg["success"]
async def test_module_log_level(hass, hass_ws_client, hass_admin_user):
"""Test setting integration log level."""
websocket_client = await hass_ws_client()
assert await async_setup_component(
hass,
"logger",
{"logger": {"logs": {"homeassistant.components.other_component": "warning"}}},
)
await websocket_client.send_json(
{
"id": 7,
"type": "logger/log_level",
"module": "homeassistant.components.websocket_api",
"level": "DEBUG",
"persistence": "none",
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 7
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
assert async_get_domain_config(hass).overrides == {
"homeassistant.components.websocket_api": logging.DEBUG,
"homeassistant.components.other_component": logging.WARNING,
}
async def test_module_log_level_override(hass, hass_ws_client, hass_admin_user):
"""Test override yaml integration log level."""
websocket_client = await hass_ws_client()
assert await async_setup_component(
hass,
"logger",
{"logger": {"logs": {"homeassistant.components.websocket_api": "warning"}}},
)
assert async_get_domain_config(hass).overrides == {
"homeassistant.components.websocket_api": logging.WARNING
}
await websocket_client.send_json(
{
"id": 6,
"type": "logger/log_level",
"module": "homeassistant.components.websocket_api",
"level": "ERROR",
"persistence": "none",
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 6
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
assert async_get_domain_config(hass).overrides == {
"homeassistant.components.websocket_api": logging.ERROR
}
await websocket_client.send_json(
{
"id": 7,
"type": "logger/log_level",
"module": "homeassistant.components.websocket_api",
"level": "DEBUG",
"persistence": "none",
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 7
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
assert async_get_domain_config(hass).overrides == {
"homeassistant.components.websocket_api": logging.DEBUG
}
await websocket_client.send_json(
{
"id": 8,
"type": "logger/log_level",
"module": "homeassistant.components.websocket_api",
"level": "NOTSET",
"persistence": "none",
}
)
msg = await websocket_client.receive_json()
assert msg["id"] == 8
assert msg["type"] == const.TYPE_RESULT
assert msg["success"]
assert async_get_domain_config(hass).overrides == {
"homeassistant.components.websocket_api": logging.NOTSET
}