cli/srecli/service/logger.py (133 lines of code) (raw):

import logging import logging.config import re from pathlib import Path from srecli.service.constants import Env LOG_FORMAT = '%(asctime)s %(levelname)s %(name)s.%(funcName)s:%(lineno)d %(message)s' class TermColor: ENDC = '\033[0m' BOLD = '\033[1m' FAIL = '\033[91m' DEBUG = '\033[90m' HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' OKGREEN = '\033[92m' WARNING = '\033[93m' UNDERLINE = '\033[4m' BOLD_RED = '\x1b[31;1m' _pattern = '{color}{string}' + ENDC @classmethod def blue(cls, st: str) -> str: return cls._pattern.format(color=cls.OKBLUE, string=st) @classmethod def cyan(cls, st: str) -> str: return cls._pattern.format(color=cls.OKCYAN, string=st) @classmethod def green(cls, st: str) -> str: return cls._pattern.format(color=cls.OKGREEN, string=st) @classmethod def yellow(cls, st: str) -> str: return cls._pattern.format(color=cls.WARNING, string=st) @classmethod def red(cls, st: str) -> str: return cls._pattern.format(color=cls.FAIL, string=st) @classmethod def gray(cls, st: str) -> str: return cls._pattern.format(color=cls.DEBUG, string=st) @classmethod def bold_red(cls, st: str) -> str: return cls._pattern.format(color=cls.BOLD_RED, string=st) class ColorFormatter(logging.Formatter): formats = { logging.DEBUG: TermColor.gray, logging.INFO: TermColor.green, logging.WARNING: TermColor.yellow, logging.ERROR: TermColor.red, logging.CRITICAL: TermColor.bold_red } def format(self, record): return self.formats[record.levelno](super().format(record)) class SensitiveFormatter(logging.Formatter): """ Formatter that removes sensitive information from jsons """ _inner = '|'.join(( 'refresh_token', 'id_token', 'password', 'authorization', 'secret', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'git_access_secret', 'api_key', 'AZURE_CLIENT_ID', 'AZURE_CLIENT_SECRET', 'GOOGLE_APPLICATION_CREDENTIALS', 'private_key', 'private_key_id', 'Authorization', 'Authentication', 'sdk_secret_key', 'key_id', 'certificate', 'access_token', 'refresh_token' )) # assuming that only raw python dicts will be written. This regex won't # catch exposed secured params inside JSON strings. In looks only for # single quotes regex = re.compile(rf"'({_inner})':\s*?'(.*?)'") def format(self, record): return re.sub( self.regex, r"'\1': '****'", super().format(record) ) config = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'base_formatter': { 'format': LOG_FORMAT, '()': SensitiveFormatter }, 'user_formatter': { 'format': '%(message)s', '()': ColorFormatter }, }, 'handlers': { 'user_handler': { 'class': 'logging.StreamHandler', 'formatter': 'user_formatter' }, 'null_handler': { 'class': 'logging.NullHandler' } }, 'loggers': { 'srecli': { 'level': Env.LOG_LEVEL.get(), 'handlers': ['null_handler'], 'propagate': False, }, 'srecli.user': { 'level': 'DEBUG', 'handlers': ['user_handler'], 'propagate': False }, } } if folder := Env.LOGS_FOLDER.get(): folder = Path(folder) if folder.is_dir(): filename = str(folder / 'srecli.log') elif folder.is_file() or folder.suffix: folder.parent.mkdir(parents=True, exist_ok=True) filename = str(folder) else: folder.mkdir(parents=True, exist_ok=True) filename = str(folder / 'srecli.log') config['handlers']['file_handler'] = { 'class': 'logging.handlers.TimedRotatingFileHandler', 'formatter': 'base_formatter', 'when': 'D', 'interval': 1, 'filename': filename } config['loggers']['srecli']['handlers'].append('file_handler') logging.captureWarnings(capture=True) logging.config.dictConfig(config) def get_logger(name: str, level: str | None = None, /) -> logging.Logger: log = logging.getLogger(name) if level: log.setLevel(level) return log def get_user_logger(): """ Colored logs to output some information to user """ return logging.getLogger('srecli.user') def enable_verbose_logs(): """ Just adds streaming handler to the main logger """ logger = logging.getLogger('srecli') handler = logging.StreamHandler() handler.setFormatter(SensitiveFormatter(LOG_FORMAT)) logger.addHandler(handler)