diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 95bb7cee24a..ac9153ce685 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -20,6 +20,7 @@ import homeassistant.loader as loader import homeassistant.util.package as pkg_util from homeassistant.util.async import ( run_coroutine_threadsafe, run_callback_threadsafe) +from homeassistant.util.logging import AsyncHandler from homeassistant.util.yaml import clear_secret_cache from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT from homeassistant.exceptions import HomeAssistantError @@ -548,8 +549,11 @@ def enable_logging(hass: core.HomeAssistant, verbose: bool=False, err_handler.setFormatter( logging.Formatter('%(asctime)s %(name)s: %(message)s', datefmt='%y-%m-%d %H:%M:%S')) + + async_handler = AsyncHandler(hass.loop, err_handler) + logger = logging.getLogger('') - logger.addHandler(err_handler) + logger.addHandler(async_handler) logger.setLevel(logging.INFO) else: diff --git a/homeassistant/util/logging.py b/homeassistant/util/logging.py index d324e7253b7..70dd9e36d21 100644 --- a/homeassistant/util/logging.py +++ b/homeassistant/util/logging.py @@ -1,5 +1,9 @@ """Logging utilities.""" +import asyncio import logging +import threading + +from .async import run_coroutine_threadsafe class HideSensitiveDataFilter(logging.Filter): @@ -15,3 +19,94 @@ class HideSensitiveDataFilter(logging.Filter): record.msg = record.msg.replace(self.text, '*******') return True + + +# pylint: disable=invalid-name +class AsyncHandler(object): + """Logging handler wrapper to add a async layer.""" + + def __init__(self, loop, handler): + """Initialize async logging handler wrapper.""" + self.handler = handler + self.loop = loop + self._queue = asyncio.Queue(loop=loop) + self._thread = threading.Thread(target=self._process) + + # Delegate from handler + self.setLevel = handler.setLevel + self.setFormatter = handler.setFormatter + self.addFilter = handler.addFilter + self.removeFilter = handler.removeFilter + self.filter = handler.filter + self.flush = handler.flush + self.handle = handler.handle + self.handleError = handler.handleError + self.format = handler.format + + def close(self): + """Wrap close to handler.""" + self.emit(None) + + def open(self): + """Wrap open to handler.""" + self._thread.start() + self.handler.open() + + def emit(self, record): + """Process a record.""" + ident = self.loop.__dict__.get("_thread_ident") + + # inside eventloop + if ident is not None and ident == threading.get_ident(): + self._queue.put_nowait(record) + # from a thread/executor + else: + self.loop.call_soon_threadsafe(self._queue.put_nowait, record) + + def __repr__(self): + """String name of this.""" + return str(self.handler) + + def _process(self): + """Process log in a thread.""" + while True: + record = run_coroutine_threadsafe( + self._queue.get(), self.loop).result() + + if record is None: + self.handler.close() + return + + self.handler.emit(record) + + def createLock(self): + """Ignore lock stuff.""" + pass + + def acquire(self): + """Ignore lock stuff.""" + pass + + def release(self): + """Ignore lock stuff.""" + pass + + @property + def level(self): + """Wrap property level to handler.""" + return self.handler.level + + @property + def formatter(self): + """Wrap property formatter to handler.""" + return self.handler.formatter + + @property + def name(self): + """Wrap property set_name to handler.""" + return self.handler.get_name() + + @name.setter + def set_name(self, name): + """Wrap property get_name to handler.""" + self.handler.name = name