Files
hasaddon/hassio-google-drive-backup/backup/logger.py
T
admins 702d080a1e New Addon
Google BK
2023-04-23 18:17:03 +07:00

218 lines
6.9 KiB
Python

import logging
from logging import LogRecord, Formatter, ERROR
from traceback import TracebackException
from colorlog import ColoredFormatter
from os.path import join, abspath
HISTORY_SIZE = 1000
PATH_BASE = abspath(join(__file__, "..", ".."))
logging.addLevelName(5, "TRACE")
logging.TRACE = 5
class HistoryHandler(logging.Handler):
def __init__(self):
super(HistoryHandler, self).__init__()
self.history = [None] * HISTORY_SIZE
self.history_index = 0
def reset(self):
self.history = [None] * HISTORY_SIZE
self.history_index = 0
def emit(self, record: LogRecord):
self.history[self.history_index % HISTORY_SIZE] = record
self.history_index += 1
def getHistory(self, start=0, html=False):
end = self.history_index
if end - start >= HISTORY_SIZE:
start = end - HISTORY_SIZE
for x in range(start, end):
item = self.history[x % HISTORY_SIZE]
if html:
if item.levelno == logging.WARN:
style = "console-warning"
elif item.levelno == logging.ERROR:
style = "console-error"
elif item.levelno == logging.DEBUG:
style = "console-debug"
elif item.levelno == logging.CRITICAL:
style = "console-critical"
elif item.levelno == logging.FATAL:
style = "console-fatal"
elif item.levelno == logging.WARNING:
style = "console-warning"
elif item.levelno == logging.TRACE:
style = "console-trace"
else:
style = "console-default"
line = "<span class='" + style + \
"'>" + self.format(item) + "</span>"
yield (x + 1, line)
else:
yield (x + 1, self.format(item))
def getLast(self) -> LogRecord:
return self.history[(self.history_index - 1) % HISTORY_SIZE]
CONSOLE = logging.StreamHandler()
CONSOLE.setLevel(logging.INFO)
formatter_color = ColoredFormatter(
'%(log_color)s%(asctime)s %(levelname)s %(message)s%(reset)s',
datefmt='%m-%d %H:%M:%S',
reset=True,
log_colors={
"DEBUG": "cyan",
"INFO": "green",
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "red",
"TRACE": "white",
},
)
CONSOLE.setFormatter(formatter_color)
HISTORY = HistoryHandler()
HISTORY.setLevel(logging.DEBUG)
HISTORY.setFormatter(Formatter('%(asctime)s %(levelname)s [%(name)s] %(message)s', '%m-%d %H:%M:%S'))
class StandardLogger(logging.Logger):
def __init__(self, name):
super().__init__(name)
self.setLevel(logging.TRACE)
self.addHandler(CONSOLE)
self.addHandler(HISTORY)
def trace(self, msg, *args, **kwargs):
self.log(logging.TRACE, msg, *args, **kwargs)
def printException(self, ex: Exception, level=ERROR):
self.log(level, self.formatException(ex))
def formatException(self, e: Exception) -> str:
trace = None
if (hasattr(e, "__traceback__")):
trace = e.__traceback__
tbe = TracebackException(type(e), e, trace, limit=None)
lines = list(self._format(tbe))
return '\n%s' % ''.join(lines)
def _format(self, tbe):
if (tbe.__context__ is not None and not tbe.__suppress_context__):
yield from self._format(tbe.__context__)
yield "Whose handling caused:\n"
is_addon, stack = self._formatStack(tbe)
yield from stack
yield from tbe.format_exception_only()
def _formatStack(self, tbe):
_RECURSIVE_CUTOFF = 3
result = []
last_file = None
last_line = None
last_name = None
count = 0
is_addon = False
buffer = []
for frame in tbe.stack:
line_internal = True
if (last_file is None or last_file != frame.filename or last_line is None or last_line != frame.lineno or last_name is None or last_name != frame.name):
if count > _RECURSIVE_CUTOFF:
count -= _RECURSIVE_CUTOFF
result.append(
f' [Previous line repeated {count} more '
f'time{"s" if count > 1 else ""}]\n'
)
last_file = frame.filename
last_line = frame.lineno
last_name = frame.name
count = 0
count += 1
if count > _RECURSIVE_CUTOFF:
continue
fileName = frame.filename
pos = fileName.rfind(PATH_BASE)
if pos >= 0:
is_addon = True
line_internal = False
fileName = "addon" + \
fileName[pos + len(PATH_BASE):]
pos = fileName.rfind("site-packages")
if pos > 0:
fileName = fileName[pos - 1:]
pos = fileName.rfind("python3.7")
if pos > 0:
fileName = fileName[pos - 1:]
pass
line = ' {}:{} ({})\n'.format(fileName, frame.lineno, frame.name)
if line_internal:
buffer.append(line)
else:
result.extend(self._compressFrames(buffer))
buffer = []
result.append(line)
if count > _RECURSIVE_CUTOFF:
count -= _RECURSIVE_CUTOFF
result.append(
f' [Previous line repeated {count} more '
f'time{"s" if count > 1 else ""}]\n'
)
result.extend(self._compressFrames(buffer))
return is_addon, result
def overrideLevel(self, console, history):
CONSOLE.setLevel(console)
HISTORY.setLevel(history)
def _compressFrames(self, buffer):
if len(buffer) > 1:
yield buffer[0]
if len(buffer) == 3:
yield buffer[1]
elif len(buffer) > 2:
yield " [{} hidden frames]\n".format(len(buffer) - 2)
yield buffer[len(buffer) - 1]
elif len(buffer) > 0:
yield buffer[len(buffer) - 1]
pass
def getLogger(name):
return StandardLogger(name)
def getHistory(index, html):
return HISTORY.getHistory(index, html)
def getLast() -> LogRecord:
return HISTORY.getLast()
def reset() -> None:
return HISTORY.reset()
class TraceLogger(StandardLogger):
def __init__(self, name):
super().__init__(name)
self.setLevel(logging.TRACE)
def log(self, lvl, msg, *args, **kwargs):
super().log(logging.TRACE, msg, *args, **kwargs)
def info(self, *args, **kwargs):
super().log(logging.TRACE, *args, **kwargs)
def error(self, *args, **kwargs):
super().log(logging.TRACE, *args, **kwargs)
def warn(self, *args, **kwargs):
super().log(logging.TRACE, *args, **kwargs)