mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 05:07:41 +00:00
Improve logging of exceptions in async_create_task (#22689)
* Improve logging of exceptions in async_create_task * Move wrapping+logging to util.logging * Minor refactor, fix typing * Add test * Remove useless @wraps, fix confusing parameter name * Review comment
This commit is contained in:
parent
75f53b2799
commit
84f778d23c
@ -24,6 +24,7 @@ from homeassistant.helpers.dispatcher import (
|
|||||||
async_dispatcher_connect, dispatcher_send)
|
async_dispatcher_connect, dispatcher_send)
|
||||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
from homeassistant.util.logging import async_create_catching_coro
|
||||||
|
|
||||||
from . import DOMAIN as CAST_DOMAIN
|
from . import DOMAIN as CAST_DOMAIN
|
||||||
|
|
||||||
@ -522,8 +523,8 @@ class CastDevice(MediaPlayerDevice):
|
|||||||
if _is_matching_dynamic_group(self._cast_info, discover):
|
if _is_matching_dynamic_group(self._cast_info, discover):
|
||||||
_LOGGER.debug("Discovered matching dynamic group: %s",
|
_LOGGER.debug("Discovered matching dynamic group: %s",
|
||||||
discover)
|
discover)
|
||||||
self.hass.async_create_task(
|
self.hass.async_create_task(async_create_catching_coro(
|
||||||
self.async_set_dynamic_group(discover))
|
self.async_set_dynamic_group(discover)))
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._cast_info.uuid != discover.uuid:
|
if self._cast_info.uuid != discover.uuid:
|
||||||
@ -536,7 +537,8 @@ class CastDevice(MediaPlayerDevice):
|
|||||||
self._cast_info.host, self._cast_info.port)
|
self._cast_info.host, self._cast_info.port)
|
||||||
return
|
return
|
||||||
_LOGGER.debug("Discovered chromecast with same UUID: %s", discover)
|
_LOGGER.debug("Discovered chromecast with same UUID: %s", discover)
|
||||||
self.hass.async_create_task(self.async_set_cast_info(discover))
|
self.hass.async_create_task(async_create_catching_coro(
|
||||||
|
self.async_set_cast_info(discover)))
|
||||||
|
|
||||||
def async_cast_removed(discover: ChromecastInfo):
|
def async_cast_removed(discover: ChromecastInfo):
|
||||||
"""Handle removal of Chromecast."""
|
"""Handle removal of Chromecast."""
|
||||||
@ -546,13 +548,15 @@ class CastDevice(MediaPlayerDevice):
|
|||||||
if (self._dynamic_group_cast_info is not None and
|
if (self._dynamic_group_cast_info is not None and
|
||||||
self._dynamic_group_cast_info.uuid == discover.uuid):
|
self._dynamic_group_cast_info.uuid == discover.uuid):
|
||||||
_LOGGER.debug("Removed matching dynamic group: %s", discover)
|
_LOGGER.debug("Removed matching dynamic group: %s", discover)
|
||||||
self.hass.async_create_task(self.async_del_dynamic_group())
|
self.hass.async_create_task(async_create_catching_coro(
|
||||||
|
self.async_del_dynamic_group()))
|
||||||
return
|
return
|
||||||
if self._cast_info.uuid != discover.uuid:
|
if self._cast_info.uuid != discover.uuid:
|
||||||
# Removed is not our device.
|
# Removed is not our device.
|
||||||
return
|
return
|
||||||
_LOGGER.debug("Removed chromecast with same UUID: %s", discover)
|
_LOGGER.debug("Removed chromecast with same UUID: %s", discover)
|
||||||
self.hass.async_create_task(self.async_del_cast_info(discover))
|
self.hass.async_create_task(async_create_catching_coro(
|
||||||
|
self.async_del_cast_info(discover)))
|
||||||
|
|
||||||
async def async_stop(event):
|
async def async_stop(event):
|
||||||
"""Disconnect socket on Home Assistant stop."""
|
"""Disconnect socket on Home Assistant stop."""
|
||||||
@ -565,14 +569,15 @@ class CastDevice(MediaPlayerDevice):
|
|||||||
self.hass, SIGNAL_CAST_REMOVED,
|
self.hass, SIGNAL_CAST_REMOVED,
|
||||||
async_cast_removed)
|
async_cast_removed)
|
||||||
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop)
|
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop)
|
||||||
self.hass.async_create_task(self.async_set_cast_info(self._cast_info))
|
self.hass.async_create_task(async_create_catching_coro(
|
||||||
|
self.async_set_cast_info(self._cast_info)))
|
||||||
for info in self.hass.data[KNOWN_CHROMECAST_INFO_KEY]:
|
for info in self.hass.data[KNOWN_CHROMECAST_INFO_KEY]:
|
||||||
if _is_matching_dynamic_group(self._cast_info, info):
|
if _is_matching_dynamic_group(self._cast_info, info):
|
||||||
_LOGGER.debug("[%s %s (%s:%s)] Found dynamic group: %s",
|
_LOGGER.debug("[%s %s (%s:%s)] Found dynamic group: %s",
|
||||||
self.entity_id, self._cast_info.friendly_name,
|
self.entity_id, self._cast_info.friendly_name,
|
||||||
self._cast_info.host, self._cast_info.port, info)
|
self._cast_info.host, self._cast_info.port, info)
|
||||||
self.hass.async_create_task(
|
self.hass.async_create_task(async_create_catching_coro(
|
||||||
self.async_set_dynamic_group(info))
|
self.async_set_dynamic_group(info)))
|
||||||
break
|
break
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self) -> None:
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
|
@ -6,7 +6,7 @@ import inspect
|
|||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Any, Callable, Optional
|
from typing import Any, Callable, Coroutine, Optional
|
||||||
|
|
||||||
from .async_ import run_coroutine_threadsafe
|
from .async_ import run_coroutine_threadsafe
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ def catch_log_exception(
|
|||||||
func: Callable[..., Any],
|
func: Callable[..., Any],
|
||||||
format_err: Callable[..., Any],
|
format_err: Callable[..., Any],
|
||||||
*args: Any) -> Callable[[], None]:
|
*args: Any) -> Callable[[], None]:
|
||||||
"""Decorate an callback to catch and log exceptions."""
|
"""Decorate a callback to catch and log exceptions."""
|
||||||
def log_exception(*args: Any) -> None:
|
def log_exception(*args: Any) -> None:
|
||||||
module_name = inspect.getmodule(inspect.trace()[1][0]).__name__
|
module_name = inspect.getmodule(inspect.trace()[1][0]).__name__
|
||||||
# Do not print the wrapper in the traceback
|
# Do not print the wrapper in the traceback
|
||||||
@ -164,3 +164,43 @@ def catch_log_exception(
|
|||||||
log_exception(*args)
|
log_exception(*args)
|
||||||
wrapper_func = wrapper
|
wrapper_func = wrapper
|
||||||
return wrapper_func
|
return wrapper_func
|
||||||
|
|
||||||
|
|
||||||
|
def catch_log_coro_exception(
|
||||||
|
target: Coroutine[Any, Any, Any],
|
||||||
|
format_err: Callable[..., Any],
|
||||||
|
*args: Any) -> Coroutine[Any, Any, Any]:
|
||||||
|
"""Decorate a coroutine to catch and log exceptions."""
|
||||||
|
async def coro_wrapper(*args: Any) -> Any:
|
||||||
|
"""Catch and log exception."""
|
||||||
|
try:
|
||||||
|
return await target
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
module_name = inspect.getmodule(inspect.trace()[1][0]).__name__
|
||||||
|
# Do not print the wrapper in the traceback
|
||||||
|
frames = len(inspect.trace()) - 1
|
||||||
|
exc_msg = traceback.format_exc(-frames)
|
||||||
|
friendly_msg = format_err(*args)
|
||||||
|
logging.getLogger(module_name).error('%s\n%s',
|
||||||
|
friendly_msg, exc_msg)
|
||||||
|
return None
|
||||||
|
return coro_wrapper()
|
||||||
|
|
||||||
|
|
||||||
|
def async_create_catching_coro(
|
||||||
|
target: Coroutine) -> Coroutine:
|
||||||
|
"""Wrap a coroutine to catch and log exceptions.
|
||||||
|
|
||||||
|
The exception will be logged together with a stacktrace of where the
|
||||||
|
coroutine was wrapped.
|
||||||
|
|
||||||
|
target: target coroutine.
|
||||||
|
"""
|
||||||
|
trace = traceback.extract_stack()
|
||||||
|
wrapped_target = catch_log_coro_exception(
|
||||||
|
target, lambda *args:
|
||||||
|
"Exception in {} called from\n {}".format(
|
||||||
|
target.__name__, # type: ignore
|
||||||
|
"".join(traceback.format_list(trace[:-1]))))
|
||||||
|
|
||||||
|
return wrapped_target
|
||||||
|
@ -65,3 +65,16 @@ def test_async_handler_thread_log(loop):
|
|||||||
|
|
||||||
assert queue.get_nowait() == log_record
|
assert queue.get_nowait() == log_record
|
||||||
assert queue.empty()
|
assert queue.empty()
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_create_catching_coro(hass, caplog):
|
||||||
|
"""Test exception logging of wrapped coroutine."""
|
||||||
|
async def job():
|
||||||
|
raise Exception('This is a bad coroutine')
|
||||||
|
pass
|
||||||
|
|
||||||
|
hass.async_create_task(logging_util.async_create_catching_coro(job()))
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert 'This is a bad coroutine' in caplog.text
|
||||||
|
assert ('hass.async_create_task('
|
||||||
|
'logging_util.async_create_catching_coro(job()))' in caplog.text)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user