py.lib.aw_log

Yohn Y. 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)