diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index 0d478ac9316..5c8fe3109a6 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -18,6 +18,9 @@ from homeassistant.components.http import HomeAssistantView import homeassistant.helpers.config_validation as cv CONF_MAX_ENTRIES = 'max_entries' +CONF_MESSAGE = 'message' +CONF_LEVEL = 'level' +CONF_LOGGER = 'logger' DATA_SYSTEM_LOG = 'system_log' DEFAULT_MAX_ENTRIES = 50 @@ -25,6 +28,7 @@ DEPENDENCIES = ['http'] DOMAIN = 'system_log' SERVICE_CLEAR = 'clear' +SERVICE_WRITE = 'write' CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ @@ -34,6 +38,12 @@ CONFIG_SCHEMA = vol.Schema({ }, extra=vol.ALLOW_EXTRA) SERVICE_CLEAR_SCHEMA = vol.Schema({}) +SERVICE_WRITE_SCHEMA = vol.Schema({ + vol.Required(CONF_MESSAGE): cv.string, + vol.Optional(CONF_LEVEL, default='error'): + vol.In(['debug', 'info', 'warning', 'error', 'critical']), + vol.Optional(CONF_LOGGER): cv.string, +}) class LogErrorHandler(logging.Handler): @@ -78,12 +88,21 @@ def async_setup(hass, config): @asyncio.coroutine def async_service_handler(service): """Handle logger services.""" - # Only one service so far - handler.records.clear() + if service.service == 'clear': + handler.records.clear() + return + if service.service == 'write': + logger = logging.getLogger( + service.data.get(CONF_LOGGER, '{}.external'.format(__name__))) + level = service.data[CONF_LEVEL] + getattr(logger, level)(service.data[CONF_MESSAGE]) hass.services.async_register( DOMAIN, SERVICE_CLEAR, async_service_handler, schema=SERVICE_CLEAR_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_WRITE, async_service_handler, + schema=SERVICE_WRITE_SCHEMA) return True diff --git a/homeassistant/components/system_log/services.yaml b/homeassistant/components/system_log/services.yaml index 98f86e12f8c..c168185c9b3 100644 --- a/homeassistant/components/system_log/services.yaml +++ b/homeassistant/components/system_log/services.yaml @@ -1,3 +1,15 @@ system_log: clear: description: Clear all log entries. + write: + description: Write log entry. + fields: + message: + description: Message to log. [Required] + example: Something went wrong + level: + description: "Log level: debug, info, warning, error, critical. Defaults to 'error'." + example: debug + logger: + description: Logger name under which to log the message. Defaults to 'system_log.external'. + example: mycomponent.myplatform diff --git a/tests/components/test_system_log.py b/tests/components/test_system_log.py index 0f61986cf47..a3e7d662483 100644 --- a/tests/components/test_system_log.py +++ b/tests/components/test_system_log.py @@ -1,11 +1,12 @@ """Test system log component.""" import asyncio import logging +from unittest.mock import MagicMock, patch + import pytest from homeassistant.bootstrap import async_setup_component from homeassistant.components import system_log -from unittest.mock import MagicMock, patch _LOGGER = logging.getLogger('test_logger') @@ -117,11 +118,54 @@ def test_clear_logs(hass, test_client): yield from get_error_log(hass, test_client, 0) +@asyncio.coroutine +def test_write_log(hass): + """Test that error propagates to logger.""" + logger = MagicMock() + with patch('logging.getLogger', return_value=logger) as mock_logging: + hass.async_add_job( + hass.services.async_call( + system_log.DOMAIN, system_log.SERVICE_WRITE, + {'message': 'test_message'})) + yield from hass.async_block_till_done() + mock_logging.assert_called_once_with( + 'homeassistant.components.system_log.external') + assert logger.method_calls[0] == ('error', ('test_message',)) + + +@asyncio.coroutine +def test_write_choose_logger(hass): + """Test that correct logger is chosen.""" + with patch('logging.getLogger') as mock_logging: + hass.async_add_job( + hass.services.async_call( + system_log.DOMAIN, system_log.SERVICE_WRITE, + {'message': 'test_message', + 'logger': 'myLogger'})) + yield from hass.async_block_till_done() + mock_logging.assert_called_once_with( + 'myLogger') + + +@asyncio.coroutine +def test_write_choose_level(hass): + """Test that correct logger is chosen.""" + logger = MagicMock() + with patch('logging.getLogger', return_value=logger): + hass.async_add_job( + hass.services.async_call( + system_log.DOMAIN, system_log.SERVICE_WRITE, + {'message': 'test_message', + 'level': 'debug'})) + yield from hass.async_block_till_done() + assert logger.method_calls[0] == ('debug', ('test_message',)) + + @asyncio.coroutine def test_unknown_path(hass, test_client): """Test error logged from unknown path.""" _LOGGER.findCaller = MagicMock( - return_value=('unknown_path', 0, None, None)) + return_value=('unknown_path', 0, None, None)) _LOGGER.error('error message') log = (yield from get_error_log(hass, test_client, 1))[0] assert log['source'] == 'unknown_path' @@ -130,16 +174,15 @@ def test_unknown_path(hass, test_client): def log_error_from_test_path(path): """Log error while mocking the path.""" call_path = 'internal_path.py' - with patch.object( - _LOGGER, - 'findCaller', - MagicMock(return_value=(call_path, 0, None, None))): + with patch.object(_LOGGER, + 'findCaller', + MagicMock(return_value=(call_path, 0, None, None))): with patch('traceback.extract_stack', MagicMock(return_value=[ - get_frame('main_path/main.py'), - get_frame(path), - get_frame(call_path), - get_frame('venv_path/logging/log.py')])): + get_frame('main_path/main.py'), + get_frame(path), + get_frame(call_path), + get_frame('venv_path/logging/log.py')])): _LOGGER.error('error message')