py.lib

Yohn Y. 2023-01-28 Parent:log/slog_console.py@fe4a96d06243 Child:bfc3a109c06c

43:6f8bea109183 Go to Latest

py.lib/log/log_confile.py

. Наведение порядка в коде логирования

History
awgur@11 1 # coding: utf-8
awgur@11 2 """ Логирование на консоль
awgur@11 3
awgur@11 4 Метки в журнале о уровне сообщения:
awgur@11 5 "`": Debug
awgur@11 6 ".": Info
awgur@11 7 "*": Warning
awgur@11 8 "!": Error
awgur@11 9 "#": Alert
awgur@43 10
awgur@11 11 """
awgur@11 12
awgur@43 13 from time import monotonic, ctime
awgur@43 14 from datetime import timedelta, date
awgur@43 15 from traceback import extract_tb, extract_stack
awgur@11 16 from sys import exc_info, stderr, stdout
awgur@43 17 from typing import Optional, TextIO, Any
awgur@43 18 from os.path import join as p_join, abspath
awgur@43 19 from threading import RLock
awgur@43 20
awgur@43 21
awgur@43 22 TIME_TO_FLUSH: int = 120 # Время по умолчанию с момента прошлого сброса лога,
awgur@43 23 # после которого, выполняется принудительный сброс буферов
awgur@11 24
awgur@11 25
awgur@11 26 class Timing(object):
awgur@43 27 def __init__(self, name: Optional[str] = None):
awgur@11 28 if name is None:
awgur@11 29 self.prefix = ''
awgur@43 30
awgur@11 31 else:
awgur@43 32 self.prefix = f'{name} :: '
awgur@43 33
awgur@30 34 self.tsAll = monotonic()
awgur@11 35 self.ts = self.tsAll
awgur@43 36
awgur@43 37 def get_time(self):
awgur@30 38 return monotonic() - self.ts
awgur@43 39
awgur@11 40 def reset(self):
awgur@30 41 self.ts = monotonic()
awgur@11 42 self.tsAll = self.ts
awgur@43 43
awgur@11 44 def __str__(self):
awgur@30 45 ts = monotonic()
awgur@11 46 return self.prefix + '%s(%.4f)' % (timedelta(seconds=(ts - self.tsAll)), ts - self.ts)
awgur@43 47
awgur@11 48 def __call__(self, msg):
awgur@43 49 _buf = f'{self} | {msg}'
awgur@30 50 self.ts = monotonic()
awgur@11 51 return _buf
awgur@11 52
awgur@11 53
awgur@43 54 class NullLog(object):
awgur@43 55 def __init__(self, prefix: str = 'main'):
awgur@43 56 self.prefix = prefix
awgur@11 57
awgur@11 58 @staticmethod
awgur@43 59 def _write(mark: str, msg: Any):
awgur@43 60 pass # cat > /dev/null
awgur@11 61
awgur@11 62 def __call__(self, msg):
awgur@43 63 self._write('.', msg)
awgur@11 64
awgur@11 65 def err(self, msg):
awgur@43 66 self._write('!', msg)
awgur@11 67
awgur@11 68 def warn(self, msg):
awgur@43 69 self._write('*', msg)
awgur@11 70
awgur@11 71 def alert(self, msg):
awgur@43 72 self._write('#', msg)
awgur@11 73
awgur@11 74 def debug(self, msg):
awgur@43 75 self._write('`', msg)
awgur@11 76
awgur@11 77 @staticmethod
awgur@43 78 def get_timing(name: Optional[str] = None):
awgur@11 79 return Timing(name)
awgur@43 80
awgur@43 81 def sub_log(self, name: str):
awgur@43 82 return self.__class__(f'{self.prefix}/{name}')
awgur@43 83
awgur@43 84 def excpt(self, msg, e_class=None, e_obj=None, e_tb=None, stack_skip=0):
awgur@43 85 if e_class is None:
awgur@43 86 e_class, e_obj, e_tb = exc_info()
awgur@43 87
awgur@43 88 tb_data_tb = list(extract_tb(e_tb))[::-1]
awgur@43 89 tb_data_stack = list(extract_stack())[::-1][(2 + stack_skip):]
awgur@11 90 self.err(msg)
awgur@11 91 self.err('--- EXCEPTION ---')
awgur@43 92 self.err(f' {e_class.__name__} ({e_obj})')
awgur@11 93 self.err('--- TRACEBACK ---')
awgur@43 94 for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_tb:
awgur@43 95 self.err(f'File: {_tb_file}, line {_tb_line} in {_tb_func}')
awgur@43 96 self.err(f' {_tb_text}')
awgur@43 97
awgur@11 98 self.err('>>> Exception Handler <<<')
awgur@43 99 for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_stack:
awgur@43 100 self.err(f'File: {_tb_file}, line {_tb_line} in {_tb_func}')
awgur@43 101 self.err(f' {_tb_text}')
awgur@43 102
awgur@11 103 self.err('--- END EXCEPTION ---')
awgur@11 104
awgur@11 105
awgur@43 106 class FileLog(NullLog):
awgur@11 107 @staticmethod
awgur@43 108 def _open_file(file_name: str):
awgur@43 109 return open(file_name, 'a', encoding='utf-8')
awgur@43 110
awgur@43 111 def __init__(self, prefix: str = 'main',
awgur@43 112 file_name: Optional[str] = None,
awgur@43 113 file_obj: Optional[TextIO] = None,
awgur@43 114 time_to_flush: int = TIME_TO_FLUSH
awgur@43 115 ):
awgur@43 116
awgur@43 117 super().__init__(prefix=prefix)
awgur@43 118 self.fd: Optional[TextIO] = None
awgur@43 119
awgur@43 120 self.flush_time = monotonic()
awgur@43 121 self.time_to_flush = time_to_flush # Время с момента прошлого сброса лога,
awgur@43 122 # после которого, выполняется принудительный сброс буферов
awgur@43 123
awgur@43 124 if file_name is not None:
awgur@43 125 self.fd = self._open_file(file_name)
awgur@43 126
awgur@43 127 else:
awgur@43 128 self.fd = file_obj
awgur@43 129
awgur@43 130 if self.fd is None:
awgur@43 131 raise ValueError(f'Не задан файл для записи журналов')
awgur@43 132
awgur@43 133 def flush(self, time_mark: float = None):
awgur@43 134 if time_mark is None:
awgur@43 135 time_mark = monotonic()
awgur@43 136
awgur@43 137 self.flush_time = time_mark
awgur@43 138 self.fd.flush()
awgur@43 139
awgur@43 140 def close(self):
awgur@43 141 self.fd.flush()
awgur@43 142 self.fd.close()
awgur@43 143 self.fd = None
awgur@43 144
awgur@43 145 def __del__(self):
awgur@43 146 if self.fd is not None:
awgur@43 147 self.fd.flush()
awgur@43 148 self.fd.close()
awgur@43 149 self.fd = None
awgur@43 150
awgur@43 151 def flush_event(self):
awgur@43 152 t = monotonic()
awgur@43 153 if t - self.flush_time >= self.time_to_flush:
awgur@43 154 self.flush(t)
awgur@43 155
awgur@43 156 def _write(self, mark: str, msg: Any):
awgur@43 157 if self.fd is None:
awgur@43 158 raise ValueError('Попытка использовать закрытый файл журнала')
awgur@43 159
awgur@43 160 t = ctime()
awgur@43 161 for l in str(msg).splitlines():
awgur@43 162 self.fd.write(f'{t} | {mark} {self.prefix} | {l}')
awgur@43 163
awgur@43 164 self.flush_event()
awgur@43 165
awgur@43 166 def sub_log(self, name: str):
awgur@43 167 if self.fd is None:
awgur@43 168 raise ValueError('Попытка использовать закрытый файл журнала')
awgur@43 169
awgur@43 170 return self.__class__(f'{self.prefix}/{name}', file_obj=self.fd, time_to_flush=self.time_to_flush)
awgur@11 171
awgur@11 172
awgur@43 173 class StderrLog(FileLog):
awgur@43 174 def __init__(self, prefix: str = 'main'):
awgur@43 175 super().__init__(prefix, file_obj=stderr)
awgur@43 176
awgur@43 177 def flush_event(self):
awgur@43 178 pass # нет необходимости сбрасывать буферы в консоли
awgur@43 179
awgur@43 180
awgur@43 181 class StdoutLog(FileLog):
awgur@43 182 def __init__(self, prefix: str = 'main'):
awgur@43 183 super().__init__(prefix, file_obj=stdout)
awgur@43 184
awgur@43 185 def flush_event(self):
awgur@43 186 pass # нет необходимости сбрасывать буферы в консоли
awgur@43 187
awgur@43 188
awgur@43 189 class LogrotateFile(FileLog):
awgur@43 190 def __init__(self, directory: str = '.', prefix: str = 'main', time_to_flush: int = TIME_TO_FLUSH):
awgur@43 191 d = date.today()
awgur@43 192 directory = abspath(directory)
awgur@43 193 file_name = p_join(directory, f'{prefix}-{d}.log')
awgur@43 194
awgur@43 195 super().__init__(prefix, file_name=file_name, time_to_flush=time_to_flush)
awgur@43 196 self.logrotate_base = p_join(directory, f'{prefix}')
awgur@43 197 self.logrotate_date = d
awgur@43 198
awgur@43 199 def flush(self, time_mark: float = None, no_rotate: bool = False):
awgur@43 200 if not no_rotate:
awgur@43 201 d = date.today()
awgur@43 202 if self.logrotate_date != d:
awgur@43 203 self.logrotate_rotate()
awgur@43 204 return
awgur@43 205
awgur@43 206 super().flush(time_mark=time_mark)
awgur@43 207
awgur@43 208 def logrotate_rotate(self):
awgur@43 209 d = date.today()
awgur@43 210 file_name = f'{self.logrotate_base}-{d}.log'
awgur@43 211 self.fd.flush()
awgur@43 212 self.fd = self._open_file(file_name)
awgur@43 213 self.logrotate_date = d
awgur@43 214 self.flush(no_rotate=True)
awgur@43 215
awgur@43 216
awgur@43 217 class ThreadSafeFileLog(FileLog):
awgur@43 218 def __int__(self, *a, **kwa):
awgur@43 219 super().__init__(*a, **kwa)
awgur@43 220 self.thread_safe_lock = RLock()
awgur@43 221
awgur@43 222 def _write(self, mark: str, msg: Any):
awgur@43 223 with self.thread_safe_lock:
awgur@43 224 super()._write(mark, msg)
awgur@43 225
awgur@43 226 def flush(self, time_mark: float = None):
awgur@43 227 with self.thread_safe_lock:
awgur@43 228 super().flush(time_mark)
awgur@43 229
awgur@43 230
awgur@43 231 class ThreadSafeLogrotateFile(LogrotateFile):
awgur@43 232 def __init__(self, directory: str = '.', prefix: str = 'main', time_to_flush: int = TIME_TO_FLUSH):
awgur@43 233 super().__init__(directory=directory, prefix=prefix, time_to_flush=time_to_flush)
awgur@43 234 self.thread_safe_lock = RLock()
awgur@43 235
awgur@43 236 def _write(self, mark: str, msg: Any):
awgur@43 237 with self.thread_safe_lock:
awgur@43 238 super()._write(mark, msg)
awgur@43 239
awgur@43 240 def flush(self, time_mark: float = None, no_rotate: bool = False):
awgur@43 241 with self.thread_safe_lock:
awgur@43 242 super().flush(time_mark=time_mark, no_rotate=no_rotate)
awgur@43 243
awgur@43 244 def logrotate_rotate(self):
awgur@43 245 with self.thread_safe_lock:
awgur@43 246 super().logrotate_rotate()