py.lib

Yohn Y. 2023-01-30 Parent:6f8bea109183

44:bfc3a109c06c 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@44 18 from os.path import join as p_join, abspath, split as p_split
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@44 131 raise ValueError('Не задан файл для записи журналов')
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@44 190 def __init__(self,
awgur@44 191 file_base: str,
awgur@44 192 file_obj: Optional[TextIO] = None,
awgur@44 193 prefix: str = 'main',
awgur@44 194 time_to_flush: int = TIME_TO_FLUSH):
awgur@44 195
awgur@43 196 d = date.today()
awgur@44 197 if file_obj is None:
awgur@44 198 super().__init__(prefix=prefix, file_name=f'{file_base}-{d}.log', time_to_flush=time_to_flush)
awgur@44 199 else:
awgur@44 200 super().__init__(prefix=prefix, file_obj=file_obj, time_to_flush=time_to_flush)
awgur@44 201
awgur@44 202 self.logrotate_base = file_base
awgur@44 203 self.logrotate_date = d
awgur@44 204
awgur@44 205 @classmethod
awgur@44 206 def make(cls, directory: str = '.', prefix: str = 'main', time_to_flush: int = TIME_TO_FLUSH):
awgur@44 207 if len(p_split(prefix)) > 1:
awgur@44 208 raise ValueError(f'Префикс журналирования не должен быть похож '
awgur@44 209 f'на пусть файловой системы, убери знак разделения директорий: {prefix}')
awgur@44 210
awgur@43 211 directory = abspath(directory)
awgur@44 212 file_base = p_join(directory, prefix)
awgur@44 213 return cls(file_base=file_base, prefix=prefix, time_to_flush=time_to_flush)
awgur@43 214
awgur@43 215 def flush(self, time_mark: float = None, no_rotate: bool = False):
awgur@43 216 if not no_rotate:
awgur@43 217 d = date.today()
awgur@43 218 if self.logrotate_date != d:
awgur@43 219 self.logrotate_rotate()
awgur@43 220 return
awgur@43 221
awgur@43 222 super().flush(time_mark=time_mark)
awgur@43 223
awgur@43 224 def logrotate_rotate(self):
awgur@43 225 d = date.today()
awgur@43 226 file_name = f'{self.logrotate_base}-{d}.log'
awgur@43 227 self.fd.flush()
awgur@43 228 self.fd = self._open_file(file_name)
awgur@43 229 self.logrotate_date = d
awgur@43 230 self.flush(no_rotate=True)
awgur@43 231
awgur@44 232 def sub_log(self, name: str):
awgur@44 233 return self.__class__(
awgur@44 234 file_base=self.logrotate_base,
awgur@44 235 file_obj=self.fd, prefix=f'{self.prefix}/{name}',
awgur@44 236 time_to_flush=self.time_to_flush
awgur@44 237 )
awgur@44 238
awgur@43 239
awgur@43 240 class ThreadSafeFileLog(FileLog):
awgur@44 241 def __int__(self, prefix: str = 'main',
awgur@44 242 file_name: Optional[str] = None,
awgur@44 243 file_obj: Optional[TextIO] = None,
awgur@44 244 time_to_flush: int = TIME_TO_FLUSH
awgur@44 245 ):
awgur@44 246 super().__init__(prefix=prefix, file_name=file_name, file_obj=file_obj, time_to_flush=time_to_flush)
awgur@43 247 self.thread_safe_lock = RLock()
awgur@43 248
awgur@43 249 def _write(self, mark: str, msg: Any):
awgur@43 250 with self.thread_safe_lock:
awgur@43 251 super()._write(mark, msg)
awgur@43 252
awgur@43 253 def flush(self, time_mark: float = None):
awgur@43 254 with self.thread_safe_lock:
awgur@43 255 super().flush(time_mark)
awgur@43 256
awgur@44 257 def sub_log(self, name: str):
awgur@44 258 if self.fd is None:
awgur@44 259 raise ValueError('Попытка использовать закрытый файл журнала')
awgur@44 260
awgur@44 261 obj = self.__class__(prefix=f'{self.prefix}/{name}', file_obj=self.fd, time_to_flush=self.time_to_flush)
awgur@44 262 obj.thread_safe_lock = self.thread_safe_lock
awgur@44 263 return obj
awgur@44 264
awgur@43 265
awgur@43 266 class ThreadSafeLogrotateFile(LogrotateFile):
awgur@44 267 def __init__(self, file_base: str, file_obj: Optional[TextIO] = None, prefix: str = 'main',
awgur@44 268 time_to_flush: int = TIME_TO_FLUSH):
awgur@44 269
awgur@44 270 super().__init__(file_base=file_base, file_obj=file_obj, prefix=prefix, time_to_flush=time_to_flush)
awgur@43 271 self.thread_safe_lock = RLock()
awgur@43 272
awgur@43 273 def _write(self, mark: str, msg: Any):
awgur@43 274 with self.thread_safe_lock:
awgur@43 275 super()._write(mark, msg)
awgur@43 276
awgur@43 277 def flush(self, time_mark: float = None, no_rotate: bool = False):
awgur@43 278 with self.thread_safe_lock:
awgur@43 279 super().flush(time_mark=time_mark, no_rotate=no_rotate)
awgur@43 280
awgur@43 281 def logrotate_rotate(self):
awgur@43 282 with self.thread_safe_lock:
awgur@43 283 super().logrotate_rotate()
awgur@44 284
awgur@44 285 def sub_log(self, name: str):
awgur@44 286 if self.fd is None:
awgur@44 287 raise ValueError('Попытка использовать закрытый файл журнала')
awgur@44 288
awgur@44 289 obj = self.__class__(file_base=self.logrotate_base,
awgur@44 290 file_obj=self.fd,
awgur@44 291 prefix=f'{self.prefix}/{name}',
awgur@44 292 time_to_flush=self.time_to_flush
awgur@44 293 )
awgur@44 294
awgur@44 295 obj.thread_safe_lock = self.thread_safe_lock
awgur@44 296 return obj