You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

152 lines
4.4 KiB

"""A cross-platform, package independant, colored stream/file logger."""
import logging
import platform
import ctypes
import ctypes.util
class _AnsiColorStreamHandler(logging.StreamHandler):
DEFAULT = '\x1b[0m'
RED = '\x1b[31m'
GREEN = '\x1b[32m'
YELLOW = '\x1b[33m'
CYAN = '\x1b[36m'
CRITICAL = RED
ERROR = RED
WARNING = YELLOW
INFO = GREEN
DEBUG = CYAN
def __init__(self, stream=None):
super().__init__(stream)
def format(self, record):
text = super().format(record)
color = self._get_color_code(record.levelno)
return color + text + self.DEFAULT
@classmethod
def _get_color_code(cls, level):
if level >= logging.CRITICAL:
return cls.CRITICAL
elif level >= logging.ERROR:
return cls.ERROR
elif level >= logging.WARNING:
return cls.WARNING
elif level >= logging.INFO:
return cls.INFO
elif level >= logging.DEBUG:
return cls.DEBUG
else:
return cls.DEFAULT
# Disable protected member access warning for MSVC functions.
# pylint: disable=W0212
class _WinColorStreamHandler(logging.StreamHandler):
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
FOREGROUND_BLACK = 0x0000
FOREGROUND_BLUE = 0x0001
FOREGROUND_GREEN = 0x0002
FOREGROUND_CYAN = 0x0003
FOREGROUND_RED = 0x0004
FOREGROUND_MAGENTA = 0x0005
FOREGROUND_YELLOW = 0x0006
FOREGROUND_GREY = 0x0007
FOREGROUND_INTENSITY = 0x0008
FOREGROUND_WHITE = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED
BACKGROUND_BLACK = 0x0000
BACKGROUND_BLUE = 0x0010
BACKGROUND_GREEN = 0x0020
BACKGROUND_CYAN = 0x0030
BACKGROUND_RED = 0x0040
BACKGROUND_MAGENTA = 0x0050
BACKGROUND_YELLOW = 0x0060
BACKGROUND_GREY = 0x0070
BACKGROUND_INTENSITY = 0x0080
DEFAULT = FOREGROUND_WHITE
CRITICAL = FOREGROUND_RED | FOREGROUND_INTENSITY
ERROR = FOREGROUND_RED | FOREGROUND_INTENSITY
WARNING = FOREGROUND_YELLOW | FOREGROUND_INTENSITY
INFO = FOREGROUND_GREEN
DEBUG = FOREGROUND_CYAN
def __init__(self, stream=None):
super().__init__(stream)
self.output_handle = self._get_output_handle(stream)
@classmethod
def _get_output_handle(cls, stream):
if stream is None:
return ctypes.windll.kernel32.GetStdHandle(cls.STD_OUTPUT_HANDLE)
else:
msvcrt_loc = ctypes.util.find_msvcrt()
msvcrt_lib = ctypes.cdll.LoadLibrary(msvcrt_loc)
return msvcrt_lib._get_osfhandle(stream.fileno())
def emit(self, record):
color_code = self._get_color_code(record.levelno)
self._set_color_code(color_code)
super().emit(record)
self._set_color_code(self.FOREGROUND_WHITE)
@classmethod
def _get_color_code(cls, level):
if level >= logging.CRITICAL:
return cls.CRITICAL
elif level >= logging.ERROR:
return cls.ERROR
elif level >= logging.WARNING:
return cls.WARNING
elif level >= logging.INFO:
return cls.INFO
elif level >= logging.DEBUG:
return cls.DEBUG
else:
return cls.DEFAULT
def _set_color_code(self, code):
ctypes.windll.kernel32.SetConsoleTextAttribute(self.output_handle, code)
if platform.system() == "Windows":
ColorStreamHandler = _WinColorStreamHandler
else:
ColorStreamHandler = _AnsiColorStreamHandler
_LOG_LEVEL = logging.DEBUG
_FORMAT = "%(asctime)s %(levelname)-8s %(message)s"
_DATE_FORMAT = "%H:%M:%S"
def get_logger(name, level=_LOG_LEVEL, log_format=_FORMAT,
date_format=_DATE_FORMAT, into_stderr=True, into_log_file=None):
logger = logging.getLogger(name)
logger.setLevel(level)
formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
if into_stderr:
stream_handler = ColorStreamHandler()
stream_handler.setLevel(level)
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
if into_log_file is not None:
file_handler = logging.FileHandler(into_log_file, mode="w")
file_handler.setLevel(level)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger