2019-06-18 16:43:02 +00:00
|
|
|
# pylint: disable=bad-whitespace
|
|
|
|
from datetime import datetime
|
2019-06-28 15:34:37 +00:00
|
|
|
from typing import TextIO, Union
|
|
|
|
from dataclasses import dataclass
|
2019-11-12 13:23:09 +00:00
|
|
|
from collections.abc import Mapping
|
2019-07-02 13:39:46 +00:00
|
|
|
from traceback import format_exception
|
2019-06-18 16:43:02 +00:00
|
|
|
from logging import DEBUG, getLogger, Handler, Formatter, Filter
|
|
|
|
|
|
|
|
|
2019-06-27 15:18:04 +00:00
|
|
|
class Color:
|
2019-07-04 15:27:25 +00:00
|
|
|
RED = '\033[31m'
|
|
|
|
GREEN = '\033[32m'
|
|
|
|
YELLOW = '\033[33m'
|
|
|
|
BLUE = '\033[34m'
|
|
|
|
CYAN = '\033[36m'
|
|
|
|
WHITE = '\033[37m'
|
|
|
|
RESET = '\033[0m'
|
2019-06-18 16:43:02 +00:00
|
|
|
|
|
|
|
|
2019-06-28 15:34:37 +00:00
|
|
|
@dataclass
|
|
|
|
class Log:
|
|
|
|
stream: Union[str, TextIO]
|
|
|
|
formatter: Formatter
|
|
|
|
|
|
|
|
|
|
|
|
class Logger:
|
|
|
|
def __init__(self, logs, level=DEBUG):
|
|
|
|
self.root_logger = getLogger()
|
|
|
|
self.old_level = self.root_logger.level
|
2019-06-18 16:43:02 +00:00
|
|
|
self.new_level = level
|
2019-06-28 15:34:37 +00:00
|
|
|
self.handlers = []
|
|
|
|
for log in logs:
|
|
|
|
handler = LogHandler(log.stream)
|
|
|
|
handler.setFormatter(log.formatter)
|
|
|
|
self.handlers.append(handler)
|
2019-06-18 16:43:02 +00:00
|
|
|
|
|
|
|
def start(self):
|
2019-06-28 15:34:37 +00:00
|
|
|
self.root_logger.setLevel(self.new_level)
|
|
|
|
for handler in self.handlers:
|
|
|
|
self.root_logger.addHandler(handler)
|
2019-06-18 16:43:02 +00:00
|
|
|
|
|
|
|
def stop(self):
|
2019-06-28 15:34:37 +00:00
|
|
|
self.root_logger.setLevel(self.old_level)
|
|
|
|
for handler in self.handlers:
|
|
|
|
handler.close()
|
|
|
|
self.root_logger.removeHandler(handler)
|
2019-06-18 16:43:02 +00:00
|
|
|
|
|
|
|
|
2019-06-28 15:34:37 +00:00
|
|
|
class LogHandler(Handler):
|
|
|
|
def __init__(self, stream):
|
|
|
|
if isinstance(stream, str):
|
|
|
|
self.stream = open(stream, 'a+')
|
|
|
|
self.close_stream = True
|
|
|
|
else:
|
|
|
|
self.stream = stream
|
|
|
|
self.close_stream = False
|
2019-06-18 16:43:02 +00:00
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
def emit(self, record):
|
2019-06-28 15:34:37 +00:00
|
|
|
entry = self.format(record)
|
|
|
|
self.stream.write(entry+'\n')
|
|
|
|
self.stream.flush()
|
2019-06-18 16:43:02 +00:00
|
|
|
|
|
|
|
def close(self):
|
2019-06-28 15:34:37 +00:00
|
|
|
if self.close_stream:
|
|
|
|
self.stream.close()
|
2019-06-18 16:43:02 +00:00
|
|
|
|
2019-06-28 15:34:37 +00:00
|
|
|
class LogFormatter(Formatter):
|
2019-06-27 12:08:39 +00:00
|
|
|
severity_to_color = {
|
2019-07-04 15:27:25 +00:00
|
|
|
'CRITICAL' : Color.RED,
|
2019-06-27 15:18:04 +00:00
|
|
|
'ERROR' : Color.RED,
|
|
|
|
'WARNING' : Color.YELLOW,
|
2019-07-04 15:27:25 +00:00
|
|
|
'INFO' : Color.GREEN,
|
|
|
|
'DEBUG' : Color.BLUE,
|
2019-06-27 15:18:04 +00:00
|
|
|
'NOTSET' : Color.CYAN
|
2019-06-18 16:43:02 +00:00
|
|
|
}
|
|
|
|
|
2019-06-27 12:08:39 +00:00
|
|
|
def __init__(self, limit):
|
|
|
|
self.limit = limit
|
2019-06-24 12:21:30 +00:00
|
|
|
super().__init__()
|
|
|
|
|
2019-06-18 16:43:02 +00:00
|
|
|
def format(self, record):
|
2019-07-04 15:27:25 +00:00
|
|
|
time = datetime.utcfromtimestamp(record.created).strftime('%H:%M:%S')
|
2019-06-18 16:43:02 +00:00
|
|
|
if record.args:
|
2019-11-12 13:23:09 +00:00
|
|
|
tuple_args = (record.args,) if isinstance(record.args, Mapping) else record.args
|
2019-06-18 16:43:02 +00:00
|
|
|
clean_args = tuple((self.trim(arg) for arg in tuple_args))
|
2019-06-28 15:34:37 +00:00
|
|
|
message = record.msg % clean_args
|
2019-06-18 16:43:02 +00:00
|
|
|
else:
|
2019-06-28 15:34:37 +00:00
|
|
|
message = record.msg
|
2019-07-02 13:39:46 +00:00
|
|
|
trace = '\n'+''.join(format_exception(*record.exc_info)) if record.exc_info else ''
|
2019-06-18 16:43:02 +00:00
|
|
|
|
2019-07-04 15:27:25 +00:00
|
|
|
return (f'[{Color.WHITE}{time}{Color.RESET}|>'
|
2019-06-28 15:34:37 +00:00
|
|
|
f'{self.severity_to_color[record.levelname]}{record.module}:'
|
2019-07-02 13:39:46 +00:00
|
|
|
f'{record.levelname.lower()}{Color.RESET}] {message}{trace}')
|
2019-06-18 16:43:02 +00:00
|
|
|
|
2019-06-27 12:08:39 +00:00
|
|
|
def trim(self, value):
|
2019-06-18 16:43:02 +00:00
|
|
|
if isinstance(value, dict):
|
2019-06-27 12:17:57 +00:00
|
|
|
return {k: self.trim(v) for k, v in value.items()}
|
2019-06-27 13:44:23 +00:00
|
|
|
if isinstance(value, str):
|
|
|
|
value_str = str(value)
|
|
|
|
return value_str if len(value_str) <= self.limit else f'{value_str[:self.limit]}...'
|
|
|
|
return value
|
2019-06-18 16:43:02 +00:00
|
|
|
|
|
|
|
|
2019-06-28 15:34:37 +00:00
|
|
|
class VerboseLogFormatter(Formatter):
|
2019-07-24 13:17:16 +00:00
|
|
|
def format(self, record): # pylint: disable=no-self-use
|
2019-06-28 15:34:37 +00:00
|
|
|
date = datetime.utcfromtimestamp(record.created).strftime('%H:%M:%S')
|
|
|
|
if record.args:
|
|
|
|
message = record.msg % record.args
|
|
|
|
else:
|
|
|
|
message = record.msg
|
2019-07-02 13:39:46 +00:00
|
|
|
trace = '\n'+''.join(format_exception(*record.exc_info)) if record.exc_info else ''
|
2019-06-28 15:34:37 +00:00
|
|
|
|
|
|
|
return (f'[{date}|>{record.module}:{record.levelname.lower()}] '
|
2019-07-02 13:39:46 +00:00
|
|
|
f'{message}{trace}')
|
2019-06-28 15:34:37 +00:00
|
|
|
|
|
|
|
|
2019-06-27 15:18:04 +00:00
|
|
|
class WhitelistFilter(Filter):
|
2019-06-18 16:43:02 +00:00
|
|
|
def __init__(self, names):
|
|
|
|
self.names = names
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
def filter(self, record):
|
|
|
|
return record.module in self.names
|
|
|
|
|
|
|
|
|
2019-06-27 15:18:04 +00:00
|
|
|
class BlacklistFilter(Filter):
|
2019-06-18 16:43:02 +00:00
|
|
|
def __init__(self, names):
|
|
|
|
self.names = names
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
def filter(self, record):
|
|
|
|
return record.module not in self.names
|