mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 19:27:45 +00:00
Add "write" service to system_log (#11901)
* Add API to write error log * Move write_error api to system_log.write service call * Restore empty line
This commit is contained in:
parent
390b727869
commit
8332d4e359
@ -18,6 +18,9 @@ from homeassistant.components.http import HomeAssistantView
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
CONF_MAX_ENTRIES = 'max_entries'
|
CONF_MAX_ENTRIES = 'max_entries'
|
||||||
|
CONF_MESSAGE = 'message'
|
||||||
|
CONF_LEVEL = 'level'
|
||||||
|
CONF_LOGGER = 'logger'
|
||||||
|
|
||||||
DATA_SYSTEM_LOG = 'system_log'
|
DATA_SYSTEM_LOG = 'system_log'
|
||||||
DEFAULT_MAX_ENTRIES = 50
|
DEFAULT_MAX_ENTRIES = 50
|
||||||
@ -25,6 +28,7 @@ DEPENDENCIES = ['http']
|
|||||||
DOMAIN = 'system_log'
|
DOMAIN = 'system_log'
|
||||||
|
|
||||||
SERVICE_CLEAR = 'clear'
|
SERVICE_CLEAR = 'clear'
|
||||||
|
SERVICE_WRITE = 'write'
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: vol.Schema({
|
DOMAIN: vol.Schema({
|
||||||
@ -34,6 +38,12 @@ CONFIG_SCHEMA = vol.Schema({
|
|||||||
}, extra=vol.ALLOW_EXTRA)
|
}, extra=vol.ALLOW_EXTRA)
|
||||||
|
|
||||||
SERVICE_CLEAR_SCHEMA = vol.Schema({})
|
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):
|
class LogErrorHandler(logging.Handler):
|
||||||
@ -78,12 +88,21 @@ def async_setup(hass, config):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_service_handler(service):
|
def async_service_handler(service):
|
||||||
"""Handle logger services."""
|
"""Handle logger services."""
|
||||||
# Only one service so far
|
if service.service == 'clear':
|
||||||
handler.records.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(
|
hass.services.async_register(
|
||||||
DOMAIN, SERVICE_CLEAR, async_service_handler,
|
DOMAIN, SERVICE_CLEAR, async_service_handler,
|
||||||
schema=SERVICE_CLEAR_SCHEMA)
|
schema=SERVICE_CLEAR_SCHEMA)
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN, SERVICE_WRITE, async_service_handler,
|
||||||
|
schema=SERVICE_WRITE_SCHEMA)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -1,3 +1,15 @@
|
|||||||
system_log:
|
system_log:
|
||||||
clear:
|
clear:
|
||||||
description: Clear all log entries.
|
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
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
"""Test system log component."""
|
"""Test system log component."""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.bootstrap import async_setup_component
|
from homeassistant.bootstrap import async_setup_component
|
||||||
from homeassistant.components import system_log
|
from homeassistant.components import system_log
|
||||||
from unittest.mock import MagicMock, patch
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger('test_logger')
|
_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)
|
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
|
@asyncio.coroutine
|
||||||
def test_unknown_path(hass, test_client):
|
def test_unknown_path(hass, test_client):
|
||||||
"""Test error logged from unknown path."""
|
"""Test error logged from unknown path."""
|
||||||
_LOGGER.findCaller = MagicMock(
|
_LOGGER.findCaller = MagicMock(
|
||||||
return_value=('unknown_path', 0, None, None))
|
return_value=('unknown_path', 0, None, None))
|
||||||
_LOGGER.error('error message')
|
_LOGGER.error('error message')
|
||||||
log = (yield from get_error_log(hass, test_client, 1))[0]
|
log = (yield from get_error_log(hass, test_client, 1))[0]
|
||||||
assert log['source'] == 'unknown_path'
|
assert log['source'] == 'unknown_path'
|
||||||
@ -130,16 +174,15 @@ def test_unknown_path(hass, test_client):
|
|||||||
def log_error_from_test_path(path):
|
def log_error_from_test_path(path):
|
||||||
"""Log error while mocking the path."""
|
"""Log error while mocking the path."""
|
||||||
call_path = 'internal_path.py'
|
call_path = 'internal_path.py'
|
||||||
with patch.object(
|
with patch.object(_LOGGER,
|
||||||
_LOGGER,
|
'findCaller',
|
||||||
'findCaller',
|
MagicMock(return_value=(call_path, 0, None, None))):
|
||||||
MagicMock(return_value=(call_path, 0, None, None))):
|
|
||||||
with patch('traceback.extract_stack',
|
with patch('traceback.extract_stack',
|
||||||
MagicMock(return_value=[
|
MagicMock(return_value=[
|
||||||
get_frame('main_path/main.py'),
|
get_frame('main_path/main.py'),
|
||||||
get_frame(path),
|
get_frame(path),
|
||||||
get_frame(call_path),
|
get_frame(call_path),
|
||||||
get_frame('venv_path/logging/log.py')])):
|
get_frame('venv_path/logging/log.py')])):
|
||||||
_LOGGER.error('error message')
|
_LOGGER.error('error message')
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user