Files
room_control/console.py
John Lancaster 68edfde755 improved logging
2024-04-27 13:41:29 -05:00

140 lines
4.0 KiB
Python

import logging
import logging.config
import re
from dataclasses import asdict, dataclass
from appdaemon.logging import AppNameFormatter
from rich.console import Console
from rich.highlighter import RegexHighlighter
from rich.logging import RichHandler
from rich.theme import Theme
class RCHighlighter(RegexHighlighter):
highlights = [
r'(?P<light>(light|switch)\.\w+)',
r'(?P<time>\d+:\d+:\d+)',
r'(?P<z2m>zigbee2mqtt/)',
r'(?P<sensor>binary_sensor\.\w+)',
# r"'state': '(?P<on>on)|(?P<off>off)'"
r'(?P<true>True)|(?P<false>False)',
]
console = Console(
width=100,
theme=Theme(
{
'log.time': 'none',
# 'logging.level.info': 'none',
'room': 'italic bright_cyan',
'component': 'dark_violet',
'friendly_name': 'yellow',
'light': 'light_slate_blue',
'sensor': 'green',
'time': 'yellow',
'z2m': 'bright_black',
'topic': 'chartreuse2',
'true': 'green',
'false': 'red',
}
),
log_time_format='%Y-%m-%d %I:%M:%S %p',
highlighter=RCHighlighter(),
)
class ContextSettingFilter(logging.Filter):
def filter(self, record: logging.LogRecord) -> logging.LogRecord:
for name, val in asdict(self).items():
setattr(record, name, val)
return record
@dataclass
class RoomControllerFilter(ContextSettingFilter):
room: str
component: str
class UnMarkupFilter(logging.Filter):
md_regex = re.compile(r'(?P<open>\[.*?\])(?P<text>.*?)(?P<close>\[\/\])')
def filter(self, record: logging.LogRecord) -> logging.LogRecord:
record.msg = self.md_regex.sub(r'\g<text>', record.msg)
return record
def create_rich_logging_dict(parent_room: str, typ: str = None):
logger_name = f'AppDaemon.{parent_room}'
if typ is not None:
logger_name += f'.{typ}'
fmt = '[room]{room:>10}[/] [component]{component:<9}[/] {message}'
else:
fmt = '[room]{room:>10}[/] {message}'
LOG_CFG = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
logger_name: {
'()': 'console.RoomControllerFilter',
'room': parent_room,
'component': typ,
},
'unmarkup': {'()': 'console.UnMarkupFilter'},
},
'formatters': {
'basic': {'style': '{', 'format': fmt, 'datefmt': '%H:%M:%S.%f'},
'err': {
'style': '{',
'format': '{asctime}.{msecs:02.0f} {room} {message}',
'datefmt': '%I:%M:%S',
},
},
'handlers': {
logger_name: {
'filters': [logger_name],
'formatter': 'basic',
'()': 'rich.logging.RichHandler',
'markup': True,
'show_path': False,
# 'show_time': False,
'omit_repeated_times': False,
'console': console,
},
'stderr': {
'filters': [logger_name, 'unmarkup'],
'formatter': 'err',
'class': 'logging.StreamHandler',
'stream': 'ext://sys.stderr',
},
},
'loggers': {
logger_name: {
'level': 'INFO',
'propagate': False,
'handlers': [
logger_name,
# 'stderr'
],
}
},
}
return LOG_CFG
def setup_component_logging(self) -> logging.Logger:
"""Creates a logger for a subcomponent with a RichHandler"""
typ = type(self).__name__
parent = self.args['app']
cfg_dict = create_rich_logging_dict(parent_room=parent, typ=typ)
logger_name = next(iter(cfg_dict['loggers']))
try:
logging.config.dictConfig(cfg_dict)
except Exception:
console.print_exception()
else:
return logging.getLogger(logger_name)