Skip to content

Commit

Permalink
Add "write" service to system_log (#11901)
Browse files Browse the repository at this point in the history
* Add API to write error log

* Move write_error api to system_log.write service call

* Restore empty line
  • Loading branch information
andrey-git authored and pvizeli committed Jan 26, 2018
1 parent 390b727 commit 8332d4e
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 12 deletions.
23 changes: 21 additions & 2 deletions homeassistant/components/system_log/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
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
DEPENDENCIES = ['http']
DOMAIN = 'system_log'

SERVICE_CLEAR = 'clear'
SERVICE_WRITE = 'write'

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
Expand All @@ -34,6 +38,12 @@
}, 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):
Expand Down Expand Up @@ -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

Expand Down
12 changes: 12 additions & 0 deletions homeassistant/components/system_log/services.yaml
Original file line number Diff line number Diff line change
@@ -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
63 changes: 53 additions & 10 deletions tests/components/test_system_log.py
Original file line number Diff line number Diff line change
@@ -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')

Expand Down Expand Up @@ -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'
Expand All @@ -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')


Expand Down

0 comments on commit 8332d4e

Please sign in to comment.