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
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/log/log_confile.py	Sat Jan 28 16:39:53 2023 +0300
     1.3 @@ -0,0 +1,246 @@
     1.4 +# coding: utf-8
     1.5 +""" Логирование на консоль
     1.6 +
     1.7 +Метки в журнале о уровне сообщения:
     1.8 +  "`": Debug
     1.9 +  ".": Info
    1.10 +  "*": Warning
    1.11 +  "!": Error
    1.12 +  "#": Alert
    1.13 +
    1.14 +"""
    1.15 +
    1.16 +from time import monotonic, ctime
    1.17 +from datetime import timedelta, date
    1.18 +from traceback import extract_tb, extract_stack
    1.19 +from sys import exc_info, stderr, stdout
    1.20 +from typing import Optional, TextIO, Any
    1.21 +from os.path import join as p_join, abspath
    1.22 +from threading import RLock
    1.23 +
    1.24 +
    1.25 +TIME_TO_FLUSH: int = 120    # Время по умолчанию с момента прошлого сброса лога,
    1.26 +                            # после которого, выполняется принудительный сброс буферов
    1.27 +
    1.28 +
    1.29 +class Timing(object):
    1.30 +    def __init__(self, name: Optional[str] = None):
    1.31 +        if name is None:
    1.32 +            self.prefix = ''
    1.33 +
    1.34 +        else:
    1.35 +            self.prefix = f'{name} :: '
    1.36 +
    1.37 +        self.tsAll = monotonic()
    1.38 +        self.ts = self.tsAll
    1.39 +
    1.40 +    def get_time(self):
    1.41 +        return monotonic() - self.ts
    1.42 +
    1.43 +    def reset(self):
    1.44 +        self.ts = monotonic()
    1.45 +        self.tsAll = self.ts
    1.46 +
    1.47 +    def __str__(self):
    1.48 +        ts = monotonic()
    1.49 +        return self.prefix + '%s(%.4f)' % (timedelta(seconds=(ts - self.tsAll)), ts - self.ts)
    1.50 +
    1.51 +    def __call__(self, msg):
    1.52 +        _buf = f'{self} | {msg}'
    1.53 +        self.ts = monotonic()
    1.54 +        return _buf
    1.55 +
    1.56 +
    1.57 +class NullLog(object):
    1.58 +    def __init__(self, prefix: str = 'main'):
    1.59 +        self.prefix = prefix
    1.60 +
    1.61 +    @staticmethod
    1.62 +    def _write(mark: str, msg: Any):
    1.63 +        pass     # cat > /dev/null
    1.64 +
    1.65 +    def __call__(self, msg):
    1.66 +        self._write('.', msg)
    1.67 +
    1.68 +    def err(self, msg):
    1.69 +        self._write('!', msg)
    1.70 +
    1.71 +    def warn(self, msg):
    1.72 +        self._write('*', msg)
    1.73 +
    1.74 +    def alert(self, msg):
    1.75 +        self._write('#', msg)
    1.76 +
    1.77 +    def debug(self, msg):
    1.78 +        self._write('`', msg)
    1.79 +
    1.80 +    @staticmethod
    1.81 +    def get_timing(name: Optional[str] = None):
    1.82 +        return Timing(name)
    1.83 +
    1.84 +    def sub_log(self, name: str):
    1.85 +        return self.__class__(f'{self.prefix}/{name}')
    1.86 +
    1.87 +    def excpt(self, msg, e_class=None, e_obj=None, e_tb=None, stack_skip=0):
    1.88 +        if e_class is None:
    1.89 +            e_class, e_obj, e_tb = exc_info()
    1.90 +
    1.91 +        tb_data_tb = list(extract_tb(e_tb))[::-1]
    1.92 +        tb_data_stack = list(extract_stack())[::-1][(2 + stack_skip):]
    1.93 +        self.err(msg)
    1.94 +        self.err('--- EXCEPTION ---')
    1.95 +        self.err(f' {e_class.__name__} ({e_obj})')
    1.96 +        self.err('--- TRACEBACK ---')
    1.97 +        for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_tb:
    1.98 +            self.err(f'File: {_tb_file}, line {_tb_line} in {_tb_func}')
    1.99 +            self.err(f'   {_tb_text}')
   1.100 +
   1.101 +        self.err('>>> Exception Handler <<<')
   1.102 +        for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_stack:
   1.103 +            self.err(f'File: {_tb_file}, line {_tb_line} in {_tb_func}')
   1.104 +            self.err(f'   {_tb_text}')
   1.105 +
   1.106 +        self.err('--- END EXCEPTION ---')
   1.107 +
   1.108 +
   1.109 +class FileLog(NullLog):
   1.110 +    @staticmethod
   1.111 +    def _open_file(file_name: str):
   1.112 +        return open(file_name, 'a', encoding='utf-8')
   1.113 +
   1.114 +    def __init__(self, prefix: str = 'main',
   1.115 +                 file_name: Optional[str] = None,
   1.116 +                 file_obj: Optional[TextIO] = None,
   1.117 +                 time_to_flush: int = TIME_TO_FLUSH
   1.118 +                 ):
   1.119 +
   1.120 +        super().__init__(prefix=prefix)
   1.121 +        self.fd: Optional[TextIO] = None
   1.122 +
   1.123 +        self.flush_time = monotonic()
   1.124 +        self.time_to_flush = time_to_flush      # Время с момента прошлого сброса лога,
   1.125 +                                                # после которого, выполняется принудительный сброс буферов
   1.126 +
   1.127 +        if file_name is not None:
   1.128 +            self.fd = self._open_file(file_name)
   1.129 +
   1.130 +        else:
   1.131 +            self.fd = file_obj
   1.132 +
   1.133 +        if self.fd is None:
   1.134 +            raise ValueError(f'Не задан файл для записи журналов')
   1.135 +
   1.136 +    def flush(self, time_mark: float = None):
   1.137 +        if time_mark is None:
   1.138 +            time_mark = monotonic()
   1.139 +
   1.140 +        self.flush_time = time_mark
   1.141 +        self.fd.flush()
   1.142 +
   1.143 +    def close(self):
   1.144 +        self.fd.flush()
   1.145 +        self.fd.close()
   1.146 +        self.fd = None
   1.147 +
   1.148 +    def __del__(self):
   1.149 +        if self.fd is not None:
   1.150 +            self.fd.flush()
   1.151 +            self.fd.close()
   1.152 +            self.fd = None
   1.153 +
   1.154 +    def flush_event(self):
   1.155 +        t = monotonic()
   1.156 +        if t - self.flush_time >= self.time_to_flush:
   1.157 +            self.flush(t)
   1.158 +
   1.159 +    def _write(self, mark: str, msg: Any):
   1.160 +        if self.fd is None:
   1.161 +            raise ValueError('Попытка использовать закрытый файл журнала')
   1.162 +
   1.163 +        t = ctime()
   1.164 +        for l in str(msg).splitlines():
   1.165 +            self.fd.write(f'{t} | {mark} {self.prefix} | {l}')
   1.166 +
   1.167 +        self.flush_event()
   1.168 +
   1.169 +    def sub_log(self, name: str):
   1.170 +        if self.fd is None:
   1.171 +            raise ValueError('Попытка использовать закрытый файл журнала')
   1.172 +
   1.173 +        return self.__class__(f'{self.prefix}/{name}', file_obj=self.fd, time_to_flush=self.time_to_flush)
   1.174 +
   1.175 +
   1.176 +class StderrLog(FileLog):
   1.177 +    def __init__(self, prefix: str = 'main'):
   1.178 +        super().__init__(prefix, file_obj=stderr)
   1.179 +
   1.180 +    def flush_event(self):
   1.181 +        pass     # нет необходимости сбрасывать буферы в консоли
   1.182 +
   1.183 +
   1.184 +class StdoutLog(FileLog):
   1.185 +    def __init__(self, prefix: str = 'main'):
   1.186 +        super().__init__(prefix, file_obj=stdout)
   1.187 +
   1.188 +    def flush_event(self):
   1.189 +        pass     # нет необходимости сбрасывать буферы в консоли
   1.190 +
   1.191 +
   1.192 +class LogrotateFile(FileLog):
   1.193 +    def __init__(self, directory: str = '.', prefix: str = 'main', time_to_flush: int = TIME_TO_FLUSH):
   1.194 +        d = date.today()
   1.195 +        directory = abspath(directory)
   1.196 +        file_name = p_join(directory, f'{prefix}-{d}.log')
   1.197 +
   1.198 +        super().__init__(prefix, file_name=file_name, time_to_flush=time_to_flush)
   1.199 +        self.logrotate_base = p_join(directory, f'{prefix}')
   1.200 +        self.logrotate_date = d
   1.201 +
   1.202 +    def flush(self, time_mark: float = None, no_rotate: bool = False):
   1.203 +        if not no_rotate:
   1.204 +            d = date.today()
   1.205 +            if self.logrotate_date != d:
   1.206 +                self.logrotate_rotate()
   1.207 +                return
   1.208 +
   1.209 +        super().flush(time_mark=time_mark)
   1.210 +
   1.211 +    def logrotate_rotate(self):
   1.212 +        d = date.today()
   1.213 +        file_name = f'{self.logrotate_base}-{d}.log'
   1.214 +        self.fd.flush()
   1.215 +        self.fd = self._open_file(file_name)
   1.216 +        self.logrotate_date = d
   1.217 +        self.flush(no_rotate=True)
   1.218 +
   1.219 +
   1.220 +class ThreadSafeFileLog(FileLog):
   1.221 +    def __int__(self, *a, **kwa):
   1.222 +        super().__init__(*a, **kwa)
   1.223 +        self.thread_safe_lock = RLock()
   1.224 +
   1.225 +    def _write(self, mark: str, msg: Any):
   1.226 +        with self.thread_safe_lock:
   1.227 +            super()._write(mark, msg)
   1.228 +
   1.229 +    def flush(self, time_mark: float = None):
   1.230 +        with self.thread_safe_lock:
   1.231 +            super().flush(time_mark)
   1.232 +
   1.233 +
   1.234 +class ThreadSafeLogrotateFile(LogrotateFile):
   1.235 +    def __init__(self, directory: str = '.', prefix: str = 'main', time_to_flush: int = TIME_TO_FLUSH):
   1.236 +        super().__init__(directory=directory, prefix=prefix, time_to_flush=time_to_flush)
   1.237 +        self.thread_safe_lock = RLock()
   1.238 +
   1.239 +    def _write(self, mark: str, msg: Any):
   1.240 +        with self.thread_safe_lock:
   1.241 +            super()._write(mark, msg)
   1.242 +
   1.243 +    def flush(self, time_mark: float = None, no_rotate: bool = False):
   1.244 +        with self.thread_safe_lock:
   1.245 +            super().flush(time_mark=time_mark, no_rotate=no_rotate)
   1.246 +
   1.247 +    def logrotate_rotate(self):
   1.248 +        with self.thread_safe_lock:
   1.249 +            super().logrotate_rotate()