diff --git a/config/.rich_logging.yaml b/config/.rich_logging.yaml deleted file mode 100644 index a6c0a63..0000000 --- a/config/.rich_logging.yaml +++ /dev/null @@ -1,14 +0,0 @@ -version: 1 -disable_existing_loggers: false -formatters: - rich: - style: "{" - format: "[room]{appname}[/] {message}" - datefmt: '%H:%M:%S.%f' -handlers: - rich: - formatter: rich - '()': 'rich.logging.RichHandler' - markup: True - show_path: false - omit_repeated_times: false diff --git a/src/room_control/.rich_logging.yaml b/src/room_control/.rich_logging.yaml new file mode 100644 index 0000000..0cbba45 --- /dev/null +++ b/src/room_control/.rich_logging.yaml @@ -0,0 +1,26 @@ +version: 1 +disable_existing_loggers: false +formatters: + rich: + style: "{" + format: "[room]{room}[/] {message}" + # format: "{message}" + datefmt: '%H:%M:%S.%f' + rich_component: + style: "{" + format: "[room]{room}[/] [component]{component}[/] {message}" + # format: "{message}" + datefmt: '%H:%M:%S.%f' +handlers: + rich: + formatter: rich + '()': 'rich.logging.RichHandler' + markup: True + show_path: false + omit_repeated_times: false + rich_component: + formatter: rich_component + '()': 'rich.logging.RichHandler' + markup: True + show_path: false + omit_repeated_times: false diff --git a/src/room_control/__init__.py b/src/room_control/__init__.py index 8f2c7c8..12ef16d 100644 --- a/src/room_control/__init__.py +++ b/src/room_control/__init__.py @@ -3,4 +3,4 @@ from .motion import Motion from .button import Button from .door import Door -__all__ = ['RoomController', 'Motion', 'Button', 'Door'] \ No newline at end of file +__all__ = ['RoomController', 'Motion', 'Button', 'Door'] diff --git a/src/room_control/button.py b/src/room_control/button.py index b4cbb0c..df8c5ec 100644 --- a/src/room_control/button.py +++ b/src/room_control/button.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, List from appdaemon.plugins.mqtt.mqttapi import Mqtt -from .console import setup_component_logging +from . import console from .model import ButtonConfig if TYPE_CHECKING: @@ -20,9 +20,9 @@ class Button(Mqtt): logger: Logger async def initialize(self): - self.config = ButtonConfig(**self.args) - self.logger = setup_component_logging(self) self.app: 'RoomController' = await self.get_app(self.args['app']) + self.logger = console.load_rich_config(self.app.name, type(self).__name__) + self.config = ButtonConfig(**self.args) self.log(f'Connected to AD app [room]{self.app.name}[/]', level='DEBUG') self.button = self.config.button diff --git a/src/room_control/console.py b/src/room_control/console.py index cfe6668..1f11d36 100644 --- a/src/room_control/console.py +++ b/src/room_control/console.py @@ -1,13 +1,13 @@ import json import logging import logging.config +from pathlib import Path import re from abc import ABC from dataclasses import asdict from importlib.resources import files import yaml -from appdaemon.adapi import ADAPI from rich.console import Console from rich.highlighter import RegexHighlighter from rich.theme import Theme @@ -46,12 +46,38 @@ class RCHighlighter(RegexHighlighter): ] -def load_rich_config(): - with files('room_control.config').joinpath('rich_logging.yaml').open('r') as f: +def load_rich_config( + room: str = None, component: str = None, level: str = 'INFO' +) -> logging.LoggerAdapter: + logger_name = f'Appdaemon.{room}' + + if component is not None: + logger_name += f'.{component}' + + with files('room_control').joinpath('.rich_logging.yaml').open('r') as f: RICH_CFG = yaml.safe_load(f) + RICH_CFG['handlers']['rich']['console'] = console RICH_CFG['handlers']['rich']['highlighter'] = RCHighlighter() - return RICH_CFG + RICH_CFG['handlers']['rich_component']['console'] = console + RICH_CFG['handlers']['rich_component']['highlighter'] = RCHighlighter() + RICH_CFG['loggers'] = { + logger_name: { + 'handlers': ['rich' if component is None else 'rich_component'], + 'propagate': False, + 'level': level, + } + } + + extra = {'room': room} + + if component is not None: + extra['component'] = component + + logging.config.dictConfig(RICH_CFG) + logger = logging.getLogger(logger_name) + adapter = logging.LoggerAdapter(logger, extra) + return adapter RICH_HANDLER_CFG = { @@ -163,51 +189,50 @@ def room_logging_config(name: str): } -def component_logging_config(parent_room: str, component: str): - logger_name = f'AppDaemon.{parent_room}.{component}' +# def component_logging_config(parent_room: str, component: str): +# logger_name = f'AppDaemon.{parent_room}.{component}' - cfg = load_rich_config() - +# cfg = load_rich_config() - LOG_CFG = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'rich_component': { - 'style': '{', - 'format': '[room]{room}[/] [component]{component}[/] {message}', - 'datefmt': '%H:%M:%S.%f', - }, - }, - 'handlers': { - 'rich_component': { - 'formatter': 'rich_component', - **RICH_HANDLER_CFG, - }, - }, - 'loggers': { - logger_name: { - # 'level': 'INFO', - 'propagate': True, - 'handlers': ['rich_component'], - } - }, - } - return LOG_CFG +# LOG_CFG = { +# 'version': 1, +# 'disable_existing_loggers': False, +# 'formatters': { +# 'rich_component': { +# 'style': '{', +# 'format': '[room]{room}[/] [component]{component}[/] {message}', +# 'datefmt': '%H:%M:%S.%f', +# }, +# }, +# 'handlers': { +# 'rich_component': { +# 'formatter': 'rich_component', +# **RICH_HANDLER_CFG, +# }, +# }, +# 'loggers': { +# logger_name: { +# # 'level': 'INFO', +# 'propagate': True, +# 'handlers': ['rich_component'], +# } +# }, +# } +# return LOG_CFG -def setup_component_logging(self) -> logging.Logger: - """Creates a logger for a subcomponent with a RichHandler""" - component = type(self).__name__ - parent = self.args['app'] - cfg_dict = component_logging_config(parent_room=parent, component=component) - logger_name = next(iter(cfg_dict['loggers'])) +# def setup_component_logging(self) -> logging.Logger: +# """Creates a logger for a subcomponent with a RichHandler""" +# component = type(self).__name__ +# parent = self.args['app'] +# cfg_dict = component_logging_config(parent_room=parent, component=component) +# logger_name = next(iter(cfg_dict['loggers'])) - try: - logging.config.dictConfig(cfg_dict) - except Exception: - console.print_exception() - else: - logger = logging.getLogger(logger_name) - logger = logging.LoggerAdapter(logger, {'room': parent, 'component': component}) - return logger +# try: +# logging.config.dictConfig(cfg_dict) +# except Exception: +# console.print_exception() +# else: +# logger = logging.getLogger(logger_name) +# logger = logging.LoggerAdapter(logger, {'room': parent, 'component': component}) +# return logger diff --git a/src/room_control/door.py b/src/room_control/door.py index bbc0456..208ba42 100644 --- a/src/room_control/door.py +++ b/src/room_control/door.py @@ -1,18 +1,23 @@ from logging import Logger +from typing import TYPE_CHECKING from appdaemon.plugins.hass.hassapi import Hass -from room_control import RoomController +from . import console -from .console import setup_component_logging +if TYPE_CHECKING: + from room_control import RoomController class Door(Hass): + app: 'RoomController' logger: Logger async def initialize(self): - self.logger = setup_component_logging(self) - self.app: RoomController = await self.get_app(self.args['app']) + self.app: 'RoomController' = await self.get_app(self.args['app']) + self.logger = console.load_rich_config( + room=self.app.name, component=type(self).__name__ + ) self.log(f'Connected to AD app [room]{self.app.name}[/]', level='DEBUG') await self.listen_state( diff --git a/src/room_control/motion.py b/src/room_control/motion.py index ba8611a..c2717c1 100644 --- a/src/room_control/motion.py +++ b/src/room_control/motion.py @@ -5,9 +5,9 @@ from typing import TYPE_CHECKING, Literal, Optional from appdaemon.entity import Entity from appdaemon.plugins.hass.hassapi import Hass -from pydantic import BaseModel, TypeAdapter, ValidationError +from pydantic import BaseModel, ValidationError -from .console import setup_component_logging +from . import console if TYPE_CHECKING: from room_control import RoomController @@ -52,9 +52,8 @@ class Motion(Hass): return self.sensor_state != self.ref_entity_state def initialize(self): - self.logger = setup_component_logging(self) self.app: 'RoomController' = self.get_app(self.args['app']) - self.log(f'Connected to AD app [room]{self.app.name}[/]', level='DEBUG') + self.logger = console.load_rich_config(self.app.name, type(self).__name__) assert self.entity_exists(self.args['sensor']) assert self.entity_exists(self.args['ref_entity']) @@ -85,6 +84,8 @@ class Motion(Hass): for handle, cb in self.callbacks(): self.log(f'Handle [yellow]{handle[:4]}[/]: {cb.function}', level='DEBUG') + + self.log(f'Initialized [bold green]{type(self).__name__}[/]') def callbacks(self): """Returns a dictionary of validated CallbackEntry objects that are associated with this app""" diff --git a/src/room_control/room_control.py b/src/room_control/room_control.py index f860f70..a5018b2 100755 --- a/src/room_control/room_control.py +++ b/src/room_control/room_control.py @@ -9,7 +9,7 @@ from appdaemon.entity import Entity from appdaemon.plugins.hass.hassapi import Hass from appdaemon.plugins.mqtt.mqttapi import Mqtt -from .console import room_logging_config +from . import console from .model import ControllerStateConfig, RoomControllerConfig logger = logging.getLogger(__name__) @@ -34,14 +34,8 @@ class RoomController(Hass, Mqtt): assert all(isinstance(s, ControllerStateConfig) for s in new), f'Invalid: {new}' self._room_config.states = new - def configure_logging(self) -> dict: - logging.config.dictConfig(room_logging_config(self.name)) - self.logger = logging.LoggerAdapter( - logging.getLogger(f'AppDaemon.{self.name}'), {'room': self.name} - ) - def initialize(self): - self.configure_logging() + self.logger = console.load_rich_config(self.name) self.app_entities = self.gather_app_entities() # self.log(f'entities: {self.app_entities}') self.refresh_state_times() @@ -206,7 +200,9 @@ class RoomController(Hass, Mqtt): elif isinstance(scene_kwargs, dict): self.call_service('scene/apply', **scene_kwargs) - self.log(f'Applied scene:\n{json.dumps(scene_kwargs, indent=2)}', level='DEBUG') + self.log( + f'Applied scene:\n{json.dumps(scene_kwargs, indent=2)}', level='DEBUG' + ) elif scene_kwargs is None: self.log('No scene, ignoring...')