py.lib.aw_log
2024-02-11
Child:9155a66edb31
py.lib.aw_log/src/aw_log/file.py
..init
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 + )