py.lib.aw_log

Yohn Y. 2024-02-11 Child:9155a66edb31

0:41b53fd5637e Go to Latest

py.lib.aw_log/src/aw_log/file.py

..init

History
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/aw_log/file.py	Sun Feb 11 13:53:23 2024 +0300
     1.3 @@ -0,0 +1,153 @@
     1.4 +# coding: utf-8
     1.5 +# devel.a0fs.ru -- aw_log.file -- v0.r202402.1
     1.6 +
     1.7 +from typing import Any
     1.8 +from time import monotonic, ctime
     1.9 +from datetime import date
    1.10 +from typing import TextIO, Optional
    1.11 +from threading import RLock
    1.12 +from os.path import join as p_join, abspath, split as p_split
    1.13 +
    1.14 +from . import AbstractLogBase
    1.15 +
    1.16 +TIME_TO_FLUSH: int = 120    # Время по умолчанию с момента прошлого сброса лога,
    1.17 +                            # после которого, выполняется принудительный сброс буферов
    1.18 +
    1.19 +
    1.20 +class NullLog(AbstractLogBase):
    1.21 +    def _write(self, mark: str, msg: Any):
    1.22 +        pass
    1.23 +
    1.24 +
    1.25 +class AbstractFileLog(AbstractLogBase):
    1.26 +    def __init__(self, prefix: str = 'main'):
    1.27 +        super().__init__(prefix)
    1.28 +        self.fd: Optional[TextIO] = None
    1.29 +        self.lock = RLock()
    1.30 +
    1.31 +    def _write(self, mark: str, msg: Any):
    1.32 +        if self.fd is None:
    1.33 +            raise ValueError(f'Не определён файл логирования')
    1.34 +
    1.35 +        self.lock.acquire()
    1.36 +        try:
    1.37 +
    1.38 +            tm = ctime()
    1.39 +            for l in super()._write_helper(mark, msg):
    1.40 +                self.fd.write(f'{tm} | {l}')
    1.41 +
    1.42 +        finally:
    1.43 +            self.lock.release()
    1.44 +
    1.45 +
    1.46 +class FileLog(AbstractFileLog):
    1.47 +    @staticmethod
    1.48 +    def _open_file(file_name: str):
    1.49 +        return open(file_name, 'a', encoding='utf-8')
    1.50 +
    1.51 +    def __init__(self, prefix: str = 'main',
    1.52 +                 file_name: Optional[str] = None,
    1.53 +                 file_obj: Optional[TextIO] = None,
    1.54 +                 time_to_flush: int = TIME_TO_FLUSH
    1.55 +                 ):
    1.56 +
    1.57 +        super().__init__(prefix=prefix)
    1.58 +        self.fd: Optional[TextIO] = None
    1.59 +
    1.60 +        self.flush_time = monotonic()
    1.61 +        self.time_to_flush = time_to_flush      # Время с момента прошлого сброса лога,
    1.62 +                                                # после которого, выполняется принудительный сброс буферов
    1.63 +
    1.64 +        if file_name is not None:
    1.65 +            self.fd = self._open_file(file_name)
    1.66 +
    1.67 +        else:
    1.68 +            self.fd = file_obj
    1.69 +
    1.70 +        if self.fd is None:
    1.71 +            raise ValueError('Не задан файл для записи журналов')
    1.72 +
    1.73 +    def flush(self, time_mark: float = None):
    1.74 +        if time_mark is None:
    1.75 +            time_mark = monotonic()
    1.76 +
    1.77 +        self.flush_time = time_mark
    1.78 +        self.fd.flush()
    1.79 +
    1.80 +    def close(self):
    1.81 +        self.fd.flush()
    1.82 +        self.fd.close()
    1.83 +        self.fd = None
    1.84 +
    1.85 +    def __del__(self):
    1.86 +        if self.fd is not None:
    1.87 +            self.fd.flush()
    1.88 +            self.fd.close()
    1.89 +            self.fd = None
    1.90 +
    1.91 +    def _flush_event(self):
    1.92 +        t = monotonic()
    1.93 +        if t - self.flush_time >= self.time_to_flush:
    1.94 +            self.flush(t)
    1.95 +
    1.96 +    def _write(self, mark: str, msg: Any):
    1.97 +        super()._write(mark=mark, msg=msg)
    1.98 +
    1.99 +        self._flush_event()
   1.100 +
   1.101 +    def sub_log(self, name: str):
   1.102 +        if self.fd is None:
   1.103 +            raise ValueError('Попытка использовать закрытый файл журнала')
   1.104 +
   1.105 +        return self.__class__(f'{self.prefix}/{name}', file_obj=self.fd, time_to_flush=self.time_to_flush)
   1.106 +
   1.107 +
   1.108 +class LogrotateFile(FileLog):
   1.109 +    def __init__(self,
   1.110 +                 file_base: str,
   1.111 +                 prefix: str = 'main',
   1.112 +                 file_obj: Optional[TextIO] = None,
   1.113 +                 time_to_flush: int = TIME_TO_FLUSH):
   1.114 +
   1.115 +        d = date.today()
   1.116 +        if file_obj is None:
   1.117 +            super().__init__(prefix=prefix, file_name=f'{file_base}-{d}.log', time_to_flush=time_to_flush)
   1.118 +        else:
   1.119 +            super().__init__(prefix=prefix, file_obj=file_obj, time_to_flush=time_to_flush)
   1.120 +
   1.121 +        self.logrotate_base = file_base
   1.122 +        self.logrotate_date = d
   1.123 +
   1.124 +    @classmethod
   1.125 +    def make(cls, directory: str = '.', prefix: str = 'main', time_to_flush: int = TIME_TO_FLUSH):
   1.126 +        if len(p_split(prefix)) > 1:
   1.127 +            raise ValueError(f'Префикс журналирования не должен быть похож '
   1.128 +                             f'на пусть файловой системы, убери знак разделения директорий: {prefix}')
   1.129 +
   1.130 +        directory = abspath(directory)
   1.131 +        file_base = p_join(directory, prefix)
   1.132 +        return cls(prefix=prefix, file_base=file_base, time_to_flush=time_to_flush)
   1.133 +
   1.134 +    def flush(self, time_mark: float = None, no_rotate: bool = False):
   1.135 +        if not no_rotate:
   1.136 +            d = date.today()
   1.137 +            if self.logrotate_date != d:
   1.138 +                self.logrotate_rotate()
   1.139 +                return
   1.140 +
   1.141 +        super().flush(time_mark=time_mark)
   1.142 +
   1.143 +    def logrotate_rotate(self):
   1.144 +        d = date.today()
   1.145 +        file_name = f'{self.logrotate_base}-{d}.log'
   1.146 +        self.fd.flush()
   1.147 +        self.fd = self._open_file(file_name)
   1.148 +        self.logrotate_date = d
   1.149 +        self.flush(no_rotate=True)
   1.150 +
   1.151 +    def sub_log(self, name: str):
   1.152 +        return self.__class__(
   1.153 +            file_base=self.logrotate_base,
   1.154 +            file_obj=self.fd, prefix=f'{self.prefix}/{name}',
   1.155 +            time_to_flush=self.time_to_flush
   1.156 +        )