"""Test system log component.""" import logging from unittest.mock import MagicMock, patch from homeassistant.core import callback from homeassistant.bootstrap import async_setup_component from homeassistant.components import system_log _LOGGER = logging.getLogger('test_logger') BASIC_CONFIG = { 'system_log': { 'max_entries': 2, } } async def get_error_log(hass, hass_client, expected_count): """Fetch all entries from system_log via the API.""" client = await hass_client() resp = await client.get('/api/error/all') assert resp.status == 200 data = await resp.json() assert len(data) == expected_count return data def _generate_and_log_exception(exception, log): try: raise Exception(exception) except: # noqa: E722 pylint: disable=bare-except _LOGGER.exception(log) def assert_log(log, exception, message, level): """Assert that specified values are in a specific log entry.""" assert exception in log['exception'] assert message == log['message'] assert level == log['level'] assert 'timestamp' in log def get_frame(name): """Get log stack frame.""" return (name, None, None, None) async def test_normal_logs(hass, hass_client): """Test that debug and info are not logged.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) _LOGGER.debug('debug') _LOGGER.info('info') # Assert done by get_error_log await get_error_log(hass, hass_client, 0) async def test_exception(hass, hass_client): """Test that exceptions are logged and retrieved correctly.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) _generate_and_log_exception('exception message', 'log message') log = (await get_error_log(hass, hass_client, 1))[0] assert_log(log, 'exception message', 'log message', 'ERROR') async def test_warning(hass, hass_client): """Test that warning are logged and retrieved correctly.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) _LOGGER.warning('warning message') log = (await get_error_log(hass, hass_client, 1))[0] assert_log(log, '', 'warning message', 'WARNING') async def test_error(hass, hass_client): """Test that errors are logged and retrieved correctly.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) _LOGGER.error('error message') log = (await get_error_log(hass, hass_client, 1))[0] assert_log(log, '', 'error message', 'ERROR') async def test_config_not_fire_event(hass): """Test that errors are not posted as events with default config.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) events = [] @callback def event_listener(event): """Listen to events of type system_log_event.""" events.append(event) hass.bus.async_listen(system_log.EVENT_SYSTEM_LOG, event_listener) _LOGGER.error('error message') await hass.async_block_till_done() assert len(events) == 0 async def test_error_posted_as_event(hass): """Test that error are posted as events.""" await async_setup_component(hass, system_log.DOMAIN, { 'system_log': { 'max_entries': 2, 'fire_event': True, } }) events = [] @callback def event_listener(event): """Listen to events of type system_log_event.""" events.append(event) hass.bus.async_listen(system_log.EVENT_SYSTEM_LOG, event_listener) _LOGGER.error('error message') await hass.async_block_till_done() assert len(events) == 1 assert_log(events[0].data, '', 'error message', 'ERROR') async def test_critical(hass, hass_client): """Test that critical are logged and retrieved correctly.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) _LOGGER.critical('critical message') log = (await get_error_log(hass, hass_client, 1))[0] assert_log(log, '', 'critical message', 'CRITICAL') async def test_remove_older_logs(hass, hass_client): """Test that older logs are rotated out.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) _LOGGER.error('error message 1') _LOGGER.error('error message 2') _LOGGER.error('error message 3') log = await get_error_log(hass, hass_client, 2) assert_log(log[0], '', 'error message 3', 'ERROR') assert_log(log[1], '', 'error message 2', 'ERROR') async def test_dedup_logs(hass, hass_client): """Test that duplicate log entries are dedup.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) _LOGGER.error('error message 1') _LOGGER.error('error message 2') _LOGGER.error('error message 2') _LOGGER.error('error message 3') log = await get_error_log(hass, hass_client, 2) assert_log(log[0], '', 'error message 3', 'ERROR') assert log[1]["count"] == 2 assert_log(log[1], '', 'error message 2', 'ERROR') _LOGGER.error('error message 2') log = await get_error_log(hass, hass_client, 2) assert_log(log[0], '', 'error message 2', 'ERROR') assert log[0]["timestamp"] > log[0]["first_occured"] async def test_clear_logs(hass, hass_client): """Test that the log can be cleared via a service call.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) _LOGGER.error('error message') hass.async_add_job( hass.services.async_call( system_log.DOMAIN, system_log.SERVICE_CLEAR, {})) await hass.async_block_till_done() # Assert done by get_error_log await get_error_log(hass, hass_client, 0) async def test_write_log(hass): """Test that error propagates to logger.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) 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'})) await hass.async_block_till_done() mock_logging.assert_called_once_with( 'homeassistant.components.system_log.external') assert logger.method_calls[0] == ('error', ('test_message',)) async def test_write_choose_logger(hass): """Test that correct logger is chosen.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) 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'})) await hass.async_block_till_done() mock_logging.assert_called_once_with( 'myLogger') async def test_write_choose_level(hass): """Test that correct logger is chosen.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) 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'})) await hass.async_block_till_done() assert logger.method_calls[0] == ('debug', ('test_message',)) async def test_unknown_path(hass, hass_client): """Test error logged from unknown path.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) _LOGGER.findCaller = MagicMock( return_value=('unknown_path', 0, None, None)) _LOGGER.error('error message') log = (await get_error_log(hass, hass_client, 1))[0] assert log['source'] == 'unknown_path' 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('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')])): _LOGGER.error('error message') async def test_homeassistant_path(hass, hass_client): """Test error logged from homeassistant path.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) with patch('homeassistant.components.system_log.HOMEASSISTANT_PATH', new=['venv_path/homeassistant']): log_error_from_test_path( 'venv_path/homeassistant/component/component.py') log = (await get_error_log(hass, hass_client, 1))[0] assert log['source'] == 'component/component.py' async def test_config_path(hass, hass_client): """Test error logged from config path.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) with patch.object(hass.config, 'config_dir', new='config'): log_error_from_test_path('config/custom_component/test.py') log = (await get_error_log(hass, hass_client, 1))[0] assert log['source'] == 'custom_component/test.py' async def test_netdisco_path(hass, hass_client): """Test error logged from netdisco path.""" await async_setup_component(hass, system_log.DOMAIN, BASIC_CONFIG) with patch.dict('sys.modules', netdisco=MagicMock(__path__=['venv_path/netdisco'])): log_error_from_test_path('venv_path/netdisco/disco_component.py') log = (await get_error_log(hass, hass_client, 1))[0] assert log['source'] == 'disco_component.py'