py.lib.aw_log
2024-02-11
Child:894923fbd0d0
0:41b53fd5637e Browse Files
..init
setup.py src/aw_log/__init__.py src/aw_log/console.py src/aw_log/file.py src/aw_log/syslog.py
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/setup.py Sun Feb 11 13:53:23 2024 +0300 1.3 @@ -0,0 +1,12 @@ 1.4 +from setuptools import setup 1.5 + 1.6 +setup( 1.7 + name='aw_log', 1.8 + version='0.202402.1', 1.9 + packages=[''], 1.10 + url='https://devel.a0fs.ru/py.lib.aw_log', 1.11 + license='BSD', 1.12 + author='awgur', 1.13 + author_email='devel@a0fs.ru', 1.14 + description='Реализация логирования' 1.15 +)
2.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 2.2 +++ b/src/aw_log/__init__.py Sun Feb 11 13:53:23 2024 +0300 2.3 @@ -0,0 +1,101 @@ 2.4 +# coding: utf-8 2.5 +# devel.a0fs.ru -- aw_log -- v0.r202402.1 2.6 +""" Реализация классов логирования 2.7 + 2.8 +Метки в журнале о уровне сообщения: 2.9 + "`": Debug 2.10 + ".": Info 2.11 + "*": Warning 2.12 + "!": Error 2.13 + "#": Alert 2.14 + 2.15 +""" 2.16 + 2.17 +from time import monotonic 2.18 +from datetime import timedelta 2.19 +from traceback import extract_tb, extract_stack 2.20 +from typing import Optional, Any, Iterable 2.21 +from sys import exc_info 2.22 + 2.23 + 2.24 +class Timing(object): 2.25 + def __init__(self, name: Optional[str] = None): 2.26 + if name is None: 2.27 + self.prefix = '' 2.28 + 2.29 + else: 2.30 + self.prefix = f'{name} :: ' 2.31 + 2.32 + self.tsAll = monotonic() 2.33 + self.ts = self.tsAll 2.34 + 2.35 + def get_time(self): 2.36 + return monotonic() - self.ts 2.37 + 2.38 + def reset(self): 2.39 + self.ts = monotonic() 2.40 + self.tsAll = self.ts 2.41 + 2.42 + def __str__(self): 2.43 + ts = monotonic() 2.44 + return self.prefix + '%s(%.4f)' % (timedelta(seconds=(ts - self.tsAll)), ts - self.ts) 2.45 + 2.46 + def __call__(self, msg): 2.47 + _buf = f'{self} | {msg}' 2.48 + self.ts = monotonic() 2.49 + return _buf 2.50 + 2.51 + 2.52 +class AbstractLogBase(object): 2.53 + def __init__(self, prefix: str = 'main'): 2.54 + self.prefix = prefix 2.55 + 2.56 + def _write_helper(self, mark: str, msg: Any) -> Iterable[str]: 2.57 + for l in str(msg).splitlines(): 2.58 + yield f'{mark} {self.prefix} | {l}' 2.59 + 2.60 + def _write(self, mark: str, msg: Any): 2.61 + raise NotImplemented(f'Метод write не определён для класса "{type(self).__name__}"') 2.62 + 2.63 + def __call__(self, msg): 2.64 + self._write('.', msg) 2.65 + 2.66 + def err(self, msg): 2.67 + self._write('!', msg) 2.68 + 2.69 + def warn(self, msg): 2.70 + self._write('*', msg) 2.71 + 2.72 + def alert(self, msg): 2.73 + self._write('#', msg) 2.74 + 2.75 + def debug(self, msg): 2.76 + self._write('`', msg) 2.77 + 2.78 + @staticmethod 2.79 + def get_timing(name: Optional[str] = None): 2.80 + return Timing(name) 2.81 + 2.82 + def sub_log(self, name: str): 2.83 + return self.__class__(f'{self.prefix}/{name}') 2.84 + 2.85 + def excpt(self, msg, e_class=None, e_obj=None, e_tb=None, stack_skip=0): 2.86 + if e_class is None: 2.87 + e_class, e_obj, e_tb = exc_info() 2.88 + 2.89 + tb_data_tb = list(extract_tb(e_tb))[::-1] 2.90 + tb_data_stack = list(extract_stack())[::-1][(2 + stack_skip):] 2.91 + self.err(msg) 2.92 + self.err('--- EXCEPTION ---') 2.93 + self.err(f' {e_class.__name__} ({e_obj})') 2.94 + self.err('--- TRACEBACK ---') 2.95 + for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_tb: 2.96 + self.err(f'File: {_tb_file}, line {_tb_line} in {_tb_func}') 2.97 + self.err(f' {_tb_text}') 2.98 + 2.99 + self.err('>>> Exception Handler <<<') 2.100 + for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_stack: 2.101 + self.err(f'File: {_tb_file}, line {_tb_line} in {_tb_func}') 2.102 + self.err(f' {_tb_text}') 2.103 + 2.104 + self.err('--- END EXCEPTION ---')
3.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 3.2 +++ b/src/aw_log/console.py Sun Feb 11 13:53:23 2024 +0300 3.3 @@ -0,0 +1,40 @@ 3.4 +# coding: utf-8 3.5 +# devel.a0fs.ru -- aw_log.console -- v0.r202402.1 3.6 + 3.7 +from typing import Any 3.8 +from time import ctime 3.9 +from sys import stderr, stdout 3.10 +from typing import TextIO, Optional 3.11 + 3.12 +from . import AbstractLogBase 3.13 + 3.14 + 3.15 +class NullLog(AbstractLogBase): 3.16 + def _write(self, mark: str, msg: Any): 3.17 + pass 3.18 + 3.19 + 3.20 +class AbstractConsoleLog(AbstractLogBase): 3.21 + def __init__(self, prefix: str = 'main'): 3.22 + super().__init__(prefix) 3.23 + self.fd: Optional[TextIO] = None 3.24 + 3.25 + def _write(self, mark: str, msg: Any): 3.26 + if self.fd is None: 3.27 + raise ValueError(f'Не определён канал логирования') 3.28 + 3.29 + tm = ctime() 3.30 + for l in super()._write_helper(mark, msg): 3.31 + self.fd.write(f'{tm} | {l}') 3.32 + 3.33 + 3.34 +class StdoutLog(AbstractConsoleLog): 3.35 + def __init__(self, prefix: str = 'main'): 3.36 + super().__init__(prefix) 3.37 + self.fd = stdout 3.38 + 3.39 + 3.40 +class StderrLog(AbstractConsoleLog): 3.41 + def __init__(self, prefix: str = 'main'): 3.42 + super().__init__(prefix) 3.43 + self.fd = stderr
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 4.2 +++ b/src/aw_log/file.py Sun Feb 11 13:53:23 2024 +0300 4.3 @@ -0,0 +1,153 @@ 4.4 +# coding: utf-8 4.5 +# devel.a0fs.ru -- aw_log.file -- v0.r202402.1 4.6 + 4.7 +from typing import Any 4.8 +from time import monotonic, ctime 4.9 +from datetime import date 4.10 +from typing import TextIO, Optional 4.11 +from threading import RLock 4.12 +from os.path import join as p_join, abspath, split as p_split 4.13 + 4.14 +from . import AbstractLogBase 4.15 + 4.16 +TIME_TO_FLUSH: int = 120 # Время по умолчанию с момента прошлого сброса лога, 4.17 + # после которого, выполняется принудительный сброс буферов 4.18 + 4.19 + 4.20 +class NullLog(AbstractLogBase): 4.21 + def _write(self, mark: str, msg: Any): 4.22 + pass 4.23 + 4.24 + 4.25 +class AbstractFileLog(AbstractLogBase): 4.26 + def __init__(self, prefix: str = 'main'): 4.27 + super().__init__(prefix) 4.28 + self.fd: Optional[TextIO] = None 4.29 + self.lock = RLock() 4.30 + 4.31 + def _write(self, mark: str, msg: Any): 4.32 + if self.fd is None: 4.33 + raise ValueError(f'Не определён файл логирования') 4.34 + 4.35 + self.lock.acquire() 4.36 + try: 4.37 + 4.38 + tm = ctime() 4.39 + for l in super()._write_helper(mark, msg): 4.40 + self.fd.write(f'{tm} | {l}') 4.41 + 4.42 + finally: 4.43 + self.lock.release() 4.44 + 4.45 + 4.46 +class FileLog(AbstractFileLog): 4.47 + @staticmethod 4.48 + def _open_file(file_name: str): 4.49 + return open(file_name, 'a', encoding='utf-8') 4.50 + 4.51 + def __init__(self, prefix: str = 'main', 4.52 + file_name: Optional[str] = None, 4.53 + file_obj: Optional[TextIO] = None, 4.54 + time_to_flush: int = TIME_TO_FLUSH 4.55 + ): 4.56 + 4.57 + super().__init__(prefix=prefix) 4.58 + self.fd: Optional[TextIO] = None 4.59 + 4.60 + self.flush_time = monotonic() 4.61 + self.time_to_flush = time_to_flush # Время с момента прошлого сброса лога, 4.62 + # после которого, выполняется принудительный сброс буферов 4.63 + 4.64 + if file_name is not None: 4.65 + self.fd = self._open_file(file_name) 4.66 + 4.67 + else: 4.68 + self.fd = file_obj 4.69 + 4.70 + if self.fd is None: 4.71 + raise ValueError('Не задан файл для записи журналов') 4.72 + 4.73 + def flush(self, time_mark: float = None): 4.74 + if time_mark is None: 4.75 + time_mark = monotonic() 4.76 + 4.77 + self.flush_time = time_mark 4.78 + self.fd.flush() 4.79 + 4.80 + def close(self): 4.81 + self.fd.flush() 4.82 + self.fd.close() 4.83 + self.fd = None 4.84 + 4.85 + def __del__(self): 4.86 + if self.fd is not None: 4.87 + self.fd.flush() 4.88 + self.fd.close() 4.89 + self.fd = None 4.90 + 4.91 + def _flush_event(self): 4.92 + t = monotonic() 4.93 + if t - self.flush_time >= self.time_to_flush: 4.94 + self.flush(t) 4.95 + 4.96 + def _write(self, mark: str, msg: Any): 4.97 + super()._write(mark=mark, msg=msg) 4.98 + 4.99 + self._flush_event() 4.100 + 4.101 + def sub_log(self, name: str): 4.102 + if self.fd is None: 4.103 + raise ValueError('Попытка использовать закрытый файл журнала') 4.104 + 4.105 + return self.__class__(f'{self.prefix}/{name}', file_obj=self.fd, time_to_flush=self.time_to_flush) 4.106 + 4.107 + 4.108 +class LogrotateFile(FileLog): 4.109 + def __init__(self, 4.110 + file_base: str, 4.111 + prefix: str = 'main', 4.112 + file_obj: Optional[TextIO] = None, 4.113 + time_to_flush: int = TIME_TO_FLUSH): 4.114 + 4.115 + d = date.today() 4.116 + if file_obj is None: 4.117 + super().__init__(prefix=prefix, file_name=f'{file_base}-{d}.log', time_to_flush=time_to_flush) 4.118 + else: 4.119 + super().__init__(prefix=prefix, file_obj=file_obj, time_to_flush=time_to_flush) 4.120 + 4.121 + self.logrotate_base = file_base 4.122 + self.logrotate_date = d 4.123 + 4.124 + @classmethod 4.125 + def make(cls, directory: str = '.', prefix: str = 'main', time_to_flush: int = TIME_TO_FLUSH): 4.126 + if len(p_split(prefix)) > 1: 4.127 + raise ValueError(f'Префикс журналирования не должен быть похож ' 4.128 + f'на пусть файловой системы, убери знак разделения директорий: {prefix}') 4.129 + 4.130 + directory = abspath(directory) 4.131 + file_base = p_join(directory, prefix) 4.132 + return cls(prefix=prefix, file_base=file_base, time_to_flush=time_to_flush) 4.133 + 4.134 + def flush(self, time_mark: float = None, no_rotate: bool = False): 4.135 + if not no_rotate: 4.136 + d = date.today() 4.137 + if self.logrotate_date != d: 4.138 + self.logrotate_rotate() 4.139 + return 4.140 + 4.141 + super().flush(time_mark=time_mark) 4.142 + 4.143 + def logrotate_rotate(self): 4.144 + d = date.today() 4.145 + file_name = f'{self.logrotate_base}-{d}.log' 4.146 + self.fd.flush() 4.147 + self.fd = self._open_file(file_name) 4.148 + self.logrotate_date = d 4.149 + self.flush(no_rotate=True) 4.150 + 4.151 + def sub_log(self, name: str): 4.152 + return self.__class__( 4.153 + file_base=self.logrotate_base, 4.154 + file_obj=self.fd, prefix=f'{self.prefix}/{name}', 4.155 + time_to_flush=self.time_to_flush 4.156 + )
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/src/aw_log/syslog.py Sun Feb 11 13:53:23 2024 +0300 5.3 @@ -0,0 +1,31 @@ 5.4 +# coding: utf-8 5.5 +# devel.a0fs.ru -- aw_log.syslog -- v0.r202402.1 5.6 + 5.7 +import syslog 5.8 +from typing import Any 5.9 + 5.10 +from . import AbstractLogBase 5.11 + 5.12 +PRIORITY_BY_MARK = { 5.13 + "`": syslog.LOG_DEBUG, # Debug 5.14 + ".": syslog.LOG_INFO, # Info 5.15 + "*": syslog.LOG_WARNING, # Warning 5.16 + "!": syslog.LOG_ERR, # Error 5.17 + "#": syslog.LOG_ALERT, # Alert 5.18 +} 5.19 + 5.20 + 5.21 +class SysLog(AbstractLogBase): 5.22 + @staticmethod 5.23 + def init_syslog(ident): 5.24 + syslog.openlog(ident, syslog.LOG_PID) 5.25 + 5.26 + def __init__(self, prefix: str = 'main', facility=syslog.LOG_USER): 5.27 + super().__init__(prefix=prefix) 5.28 + self.facility = facility 5.29 + 5.30 + def _write(self, mark: str, msg: Any): 5.31 + flag = self.facility | PRIORITY_BY_MARK.get(mark, syslog.LOG_INFO) 5.32 + 5.33 + for l in self._write_helper(mark=mark, msg=msg): 5.34 + syslog.syslog(flag, l)