import traceback from contextlib import contextmanager from contextvars import ContextVar from uuid import uuid4 _ACTIVE_LOG_GROUP = ContextVar('appdaemon_log_group', default=None) @contextmanager def log_group(kind, label=None, *, inherit=True): """Tag LoggerInterface messages in this synchronous operation with one group ID.""" parent = _ACTIVE_LOG_GROUP.get() if inherit else None group = parent or { 'id': uuid4().hex[:12], 'kind': kind, 'label': str(label) if label is not None else '', } token = _ACTIVE_LOG_GROUP.set(group) try: yield group finally: _ACTIVE_LOG_GROUP.reset(token) class LoggerInterface: def __init__(self): self.initialized = False def __init__(self, ad_api, default_log='main_log', mute=False, *, ascii_encode=None): self.initialize_logger_interface(ad_api, default_log, mute, ascii_encode=ascii_encode) def initialize_logger_interface(self, ad_api, default_log='main_log', mute=False, *, ascii_encode=None): self.ad_api = ad_api self.default_log = default_log self.mute = False self.initialized = True self._log_options = {} if ascii_encode is None else {'ascii_encode': ascii_encode} def _write_log(self, message, level, log): self.ad_api.log(self._tag_log_message(message), level=level, log=log, **self._log_options) def log_group(self, kind, label=None): return log_group(kind, label) @staticmethod def _tag_log_message(message): group = _ACTIVE_LOG_GROUP.get() if group is None: return message marker = f"[group={group['kind']}:{group['id']}]" if isinstance(message, str) and marker in message: return message return f"{message} {marker}" def mute_logger(self, mute: bool): if mute: self._write_log('Muting logger', 'INFO', self.default_log) self.mute = bool(mute) def log_info(self, message): assert self.initialized if not self.mute: self._write_log(message, 'INFO', self.default_log) def log_warning(self, message): assert self.initialized self._write_log(message, 'WARNING', self.default_log) def log_error(self, message, stop_app=False, dump_stack=False): assert self.initialized if dump_stack: stack = '\n' for line in traceback.format_stack()[:-1]: stack += line.strip() + '\n' else: stack = '' message = self._tag_log_message(message) self._write_log(message, 'ERROR', self.default_log) self._write_log(message + stack, 'ERROR', 'error_log') if stop_app: self.stop_app(self.name)