apv- 🐍 advanced python logging 📔 |
git clone git://git.acid.vegas/apv.git |
Log | Files | Refs | Archive | README | LICENSE |
apv.py (7968B)
1 #!/usr/bin/env python3 2 # Advanced Python Logging - Developed by acidvegas in Python (https://git.acid.vegas/apv) 3 # apv.py 4 5 import gzip 6 import json 7 import logging 8 import logging.handlers 9 import os 10 import socket 11 import sys 12 13 14 class LogColors: 15 '''ANSI color codes for log messages''' 16 17 NOTSET = '\033[97m' # White text 18 DEBUG = '\033[96m' # Cyan 19 INFO = '\033[92m' # Green 20 WARNING = '\033[93m' # Yellow 21 ERROR = '\033[91m' # Red 22 CRITICAL = '\033[97m\033[41m' # White on Red 23 FATAL = '\033[97m\033[41m' # Same as CRITICAL 24 DATE = '\033[90m' # Dark Grey 25 MODULE = '\033[95m' # Pink 26 FUNCTION = '\033[94m' # Blue 27 LINE = '\033[33m' # Orange 28 RESET = '\033[0m' 29 SEPARATOR = '\033[90m' # Dark Grey 30 31 32 class ConsoleFormatter(logging.Formatter): 33 '''A formatter for the consolethat supports colored output''' 34 35 def __init__(self, datefmt: str = None, details: bool = False): 36 super().__init__(datefmt=datefmt) 37 self.details = details 38 39 40 def format(self, record: logging.LogRecord) -> str: 41 ''' 42 Format a log record for the console 43 44 :param record: The log record to format 45 ''' 46 47 # Get the color for the log level 48 color = getattr(LogColors, record.levelname, LogColors.RESET) 49 50 # Format the log level 51 log_level = f'{color}{record.levelname:<8}{LogColors.RESET}' 52 53 # Get the log message 54 message = record.getMessage() 55 56 # Format the timestamp 57 asctime = f'{LogColors.DATE}{self.formatTime(record, self.datefmt)}' 58 59 # Get the separator 60 separator = f'{LogColors.SEPARATOR} ┃ {LogColors.RESET}' 61 details = f'{LogColors.MODULE}{record.module}{separator}{LogColors.FUNCTION}{record.funcName}{separator}{LogColors.LINE}{record.lineno}{separator}' if self.details else '' 62 63 return f'{asctime}{separator}{log_level}{separator}{details}{message}' 64 65 66 class JsonFormatter(logging.Formatter): 67 '''Formatter for JSON output''' 68 69 def __init__(self, datefmt: str = None): 70 super().__init__(datefmt=datefmt) 71 72 73 def format(self, record: logging.LogRecord) -> str: 74 ''' 75 Format a log record for JSON output 76 77 :param record: The log record to format 78 ''' 79 80 # Create a dictionary to store the log record 81 log_dict = { 82 '@timestamp' : self.formatTime(record, self.datefmt), 83 'level' : record.levelname, 84 'message' : record.getMessage(), 85 'process_id' : record.process, 86 'process_name' : record.processName, 87 'thread_id' : record.thread, 88 'thread_name' : record.threadName, 89 'logger_name' : record.name, 90 'filename' : record.filename, 91 'line_number' : record.lineno, 92 'function' : record.funcName, 93 'module' : record.module, 94 'hostname' : socket.gethostname() 95 } 96 97 # Add the exception if it exists 98 if record.exc_info: 99 log_dict['exception'] = self.formatException(record.exc_info) 100 101 # Add any custom attributes that start with an underscore 102 custom_attrs = {k: v for k, v in record.__dict__.items() if k.startswith('_') and not k.startswith('__')} 103 log_dict.update(custom_attrs) 104 105 return json.dumps(log_dict) 106 107 108 class GZipRotatingFileHandler(logging.handlers.RotatingFileHandler): 109 '''RotatingFileHandler that compresses rotated log files''' 110 111 def rotation_filename(self, default_name: str) -> str: 112 return default_name + '.gz' 113 114 def rotate(self, source: str, dest: str): 115 with open(source, 'rb') as src, gzip.open(dest, 'wb') as dst: 116 dst.write(src.read()) 117 118 119 class LoggerSetup: 120 def __init__(self, level: str = 'INFO', date_format: str = '%Y-%m-%d %H:%M:%S', log_to_disk: bool = False, max_log_size: int = 10*1024*1024, max_backups: int = 7, log_file_name: str = 'app', json_log: bool = False, show_details: bool = False, compress_backups: bool = False): 121 ''' 122 Initialize the LoggerSetup with provided parameters 123 124 :param level: The logging level (e.g., 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL') 125 :param date_format: The date format for log messages 126 :param log_to_disk: Whether to log to disk 127 :param max_log_size: The maximum size of log files before rotation 128 :param max_backups: The maximum number of backup log files to keep 129 :param log_file_name: The base name of the log file 130 :param json_log: Whether to log in JSON format 131 :param show_details: Whether to show detailed log messages 132 :param compress_backups: Whether to compress old log files using gzip 133 ''' 134 135 self.level = level 136 self.date_format = date_format 137 self.log_to_disk = log_to_disk 138 self.max_log_size = max_log_size 139 self.max_backups = max_backups 140 self.log_file_name = log_file_name 141 self.json_log = json_log 142 self.show_details = show_details 143 self.compress_backups = compress_backups 144 145 146 def setup(self): 147 '''Set up logging with various handlers and options''' 148 149 # Clear existing handlers 150 logging.getLogger().handlers.clear() 151 logging.getLogger().setLevel(logging.DEBUG) 152 153 # Convert the level string to a logging level object 154 level_num = getattr(logging, self.level.upper(), logging.INFO) 155 156 # Setup console handler 157 self.setup_console_handler(level_num) 158 159 # Setup file handler if enabled 160 if self.log_to_disk: 161 self.setup_file_handler(level_num) 162 163 164 def setup_console_handler(self, level_num: int): 165 ''' 166 Set up the console handler 167 168 :param level_num: The logging level number 169 ''' 170 171 # Create the console handler 172 console_handler = logging.StreamHandler() 173 console_handler.setLevel(level_num) 174 175 # Create the formatter 176 formatter = JsonFormatter(datefmt=self.date_format) if self.json_log else ConsoleFormatter(datefmt=self.date_format, details=self.show_details) 177 console_handler.setFormatter(formatter) 178 179 # Add the handler to the root logger 180 logging.getLogger().addHandler(console_handler) 181 182 183 def setup_file_handler(self, level_num: int): 184 ''' 185 Set up the file handler 186 187 :param level_num: The logging level number 188 ''' 189 190 # Create logs directory if it doesn't exist 191 logs_dir = os.path.join(sys.path[0], 'logs') 192 os.makedirs(logs_dir, exist_ok=True) 193 194 # Set up log file path 195 file_extension = '.json' if self.json_log else '.log' 196 log_file_path = os.path.join(logs_dir, f'{self.log_file_name}{file_extension}') 197 198 # Create the rotating file handler 199 handler_class = GZipRotatingFileHandler if self.compress_backups else logging.handlers.RotatingFileHandler 200 file_handler = handler_class(log_file_path, maxBytes=self.max_log_size, backupCount=self.max_backups) 201 file_handler.setLevel(level_num) 202 203 # Set up the appropriate formatter 204 formatter = JsonFormatter(datefmt=self.date_format) if self.json_log else logging.Formatter(fmt='%(asctime)s ┃ %(levelname)-8s ┃ %(module)s ┃ %(funcName)s ┃ %(lineno)d ┃ %(message)s', datefmt=self.date_format) 205 file_handler.setFormatter(formatter) 206 207 logging.getLogger().addHandler(file_handler) 208 209 210 def setup_logging(**kwargs): 211 '''Set up logging with various handlers and options''' 212 213 # Create a LoggerSetup instance with the provided keyword arguments 214 logger_setup = LoggerSetup(**kwargs) 215 216 # Set up the logging system 217 logger_setup.setup()