py.lib

Yohn Y. 2023-01-30 Parent:6f8bea109183

44:bfc3a109c06c Go to Latest

py.lib/log/log_confile.py

. Убираем лишние форматированные строки * Проблема с созданием вложенных журналов в сложных ситуациях с локами и ротируемым журналом.

History
1 # coding: utf-8
2 """ Логирование на консоль
4 Метки в журнале о уровне сообщения:
5 "`": Debug
6 ".": Info
7 "*": Warning
8 "!": Error
9 "#": Alert
11 """
13 from time import monotonic, ctime
14 from datetime import timedelta, date
15 from traceback import extract_tb, extract_stack
16 from sys import exc_info, stderr, stdout
17 from typing import Optional, TextIO, Any
18 from os.path import join as p_join, abspath, split as p_split
19 from threading import RLock
22 TIME_TO_FLUSH: int = 120 # Время по умолчанию с момента прошлого сброса лога,
23 # после которого, выполняется принудительный сброс буферов
26 class Timing(object):
27 def __init__(self, name: Optional[str] = None):
28 if name is None:
29 self.prefix = ''
31 else:
32 self.prefix = f'{name} :: '
34 self.tsAll = monotonic()
35 self.ts = self.tsAll
37 def get_time(self):
38 return monotonic() - self.ts
40 def reset(self):
41 self.ts = monotonic()
42 self.tsAll = self.ts
44 def __str__(self):
45 ts = monotonic()
46 return self.prefix + '%s(%.4f)' % (timedelta(seconds=(ts - self.tsAll)), ts - self.ts)
48 def __call__(self, msg):
49 _buf = f'{self} | {msg}'
50 self.ts = monotonic()
51 return _buf
54 class NullLog(object):
55 def __init__(self, prefix: str = 'main'):
56 self.prefix = prefix
58 @staticmethod
59 def _write(mark: str, msg: Any):
60 pass # cat > /dev/null
62 def __call__(self, msg):
63 self._write('.', msg)
65 def err(self, msg):
66 self._write('!', msg)
68 def warn(self, msg):
69 self._write('*', msg)
71 def alert(self, msg):
72 self._write('#', msg)
74 def debug(self, msg):
75 self._write('`', msg)
77 @staticmethod
78 def get_timing(name: Optional[str] = None):
79 return Timing(name)
81 def sub_log(self, name: str):
82 return self.__class__(f'{self.prefix}/{name}')
84 def excpt(self, msg, e_class=None, e_obj=None, e_tb=None, stack_skip=0):
85 if e_class is None:
86 e_class, e_obj, e_tb = exc_info()
88 tb_data_tb = list(extract_tb(e_tb))[::-1]
89 tb_data_stack = list(extract_stack())[::-1][(2 + stack_skip):]
90 self.err(msg)
91 self.err('--- EXCEPTION ---')
92 self.err(f' {e_class.__name__} ({e_obj})')
93 self.err('--- TRACEBACK ---')
94 for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_tb:
95 self.err(f'File: {_tb_file}, line {_tb_line} in {_tb_func}')
96 self.err(f' {_tb_text}')
98 self.err('>>> Exception Handler <<<')
99 for _tb_file, _tb_line, _tb_func, _tb_text in tb_data_stack:
100 self.err(f'File: {_tb_file}, line {_tb_line} in {_tb_func}')
101 self.err(f' {_tb_text}')
103 self.err('--- END EXCEPTION ---')
106 class FileLog(NullLog):
107 @staticmethod
108 def _open_file(file_name: str):
109 return open(file_name, 'a', encoding='utf-8')
111 def __init__(self, prefix: str = 'main',
112 file_name: Optional[str] = None,
113 file_obj: Optional[TextIO] = None,
114 time_to_flush: int = TIME_TO_FLUSH
115 ):
117 super().__init__(prefix=prefix)
118 self.fd: Optional[TextIO] = None
120 self.flush_time = monotonic()
121 self.time_to_flush = time_to_flush # Время с момента прошлого сброса лога,
122 # после которого, выполняется принудительный сброс буферов
124 if file_name is not None:
125 self.fd = self._open_file(file_name)
127 else:
128 self.fd = file_obj
130 if self.fd is None:
131 raise ValueError('Не задан файл для записи журналов')
133 def flush(self, time_mark: float = None):
134 if time_mark is None:
135 time_mark = monotonic()
137 self.flush_time = time_mark
138 self.fd.flush()
140 def close(self):
141 self.fd.flush()
142 self.fd.close()
143 self.fd = None
145 def __del__(self):
146 if self.fd is not None:
147 self.fd.flush()
148 self.fd.close()
149 self.fd = None
151 def flush_event(self):
152 t = monotonic()
153 if t - self.flush_time >= self.time_to_flush:
154 self.flush(t)
156 def _write(self, mark: str, msg: Any):
157 if self.fd is None:
158 raise ValueError('Попытка использовать закрытый файл журнала')
160 t = ctime()
161 for l in str(msg).splitlines():
162 self.fd.write(f'{t} | {mark} {self.prefix} | {l}')
164 self.flush_event()
166 def sub_log(self, name: str):
167 if self.fd is None:
168 raise ValueError('Попытка использовать закрытый файл журнала')
170 return self.__class__(f'{self.prefix}/{name}', file_obj=self.fd, time_to_flush=self.time_to_flush)
173 class StderrLog(FileLog):
174 def __init__(self, prefix: str = 'main'):
175 super().__init__(prefix, file_obj=stderr)
177 def flush_event(self):
178 pass # нет необходимости сбрасывать буферы в консоли
181 class StdoutLog(FileLog):
182 def __init__(self, prefix: str = 'main'):
183 super().__init__(prefix, file_obj=stdout)
185 def flush_event(self):
186 pass # нет необходимости сбрасывать буферы в консоли
189 class LogrotateFile(FileLog):
190 def __init__(self,
191 file_base: str,
192 file_obj: Optional[TextIO] = None,
193 prefix: str = 'main',
194 time_to_flush: int = TIME_TO_FLUSH):
196 d = date.today()
197 if file_obj is None:
198 super().__init__(prefix=prefix, file_name=f'{file_base}-{d}.log', time_to_flush=time_to_flush)
199 else:
200 super().__init__(prefix=prefix, file_obj=file_obj, time_to_flush=time_to_flush)
202 self.logrotate_base = file_base
203 self.logrotate_date = d
205 @classmethod
206 def make(cls, directory: str = '.', prefix: str = 'main', time_to_flush: int = TIME_TO_FLUSH):
207 if len(p_split(prefix)) > 1:
208 raise ValueError(f'Префикс журналирования не должен быть похож '
209 f'на пусть файловой системы, убери знак разделения директорий: {prefix}')
211 directory = abspath(directory)
212 file_base = p_join(directory, prefix)
213 return cls(file_base=file_base, prefix=prefix, time_to_flush=time_to_flush)
215 def flush(self, time_mark: float = None, no_rotate: bool = False):
216 if not no_rotate:
217 d = date.today()
218 if self.logrotate_date != d:
219 self.logrotate_rotate()
220 return
222 super().flush(time_mark=time_mark)
224 def logrotate_rotate(self):
225 d = date.today()
226 file_name = f'{self.logrotate_base}-{d}.log'
227 self.fd.flush()
228 self.fd = self._open_file(file_name)
229 self.logrotate_date = d
230 self.flush(no_rotate=True)
232 def sub_log(self, name: str):
233 return self.__class__(
234 file_base=self.logrotate_base,
235 file_obj=self.fd, prefix=f'{self.prefix}/{name}',
236 time_to_flush=self.time_to_flush
240 class ThreadSafeFileLog(FileLog):
241 def __int__(self, prefix: str = 'main',
242 file_name: Optional[str] = None,
243 file_obj: Optional[TextIO] = None,
244 time_to_flush: int = TIME_TO_FLUSH
245 ):
246 super().__init__(prefix=prefix, file_name=file_name, file_obj=file_obj, time_to_flush=time_to_flush)
247 self.thread_safe_lock = RLock()
249 def _write(self, mark: str, msg: Any):
250 with self.thread_safe_lock:
251 super()._write(mark, msg)
253 def flush(self, time_mark: float = None):
254 with self.thread_safe_lock:
255 super().flush(time_mark)
257 def sub_log(self, name: str):
258 if self.fd is None:
259 raise ValueError('Попытка использовать закрытый файл журнала')
261 obj = self.__class__(prefix=f'{self.prefix}/{name}', file_obj=self.fd, time_to_flush=self.time_to_flush)
262 obj.thread_safe_lock = self.thread_safe_lock
263 return obj
266 class ThreadSafeLogrotateFile(LogrotateFile):
267 def __init__(self, file_base: str, file_obj: Optional[TextIO] = None, prefix: str = 'main',
268 time_to_flush: int = TIME_TO_FLUSH):
270 super().__init__(file_base=file_base, file_obj=file_obj, prefix=prefix, time_to_flush=time_to_flush)
271 self.thread_safe_lock = RLock()
273 def _write(self, mark: str, msg: Any):
274 with self.thread_safe_lock:
275 super()._write(mark, msg)
277 def flush(self, time_mark: float = None, no_rotate: bool = False):
278 with self.thread_safe_lock:
279 super().flush(time_mark=time_mark, no_rotate=no_rotate)
281 def logrotate_rotate(self):
282 with self.thread_safe_lock:
283 super().logrotate_rotate()
285 def sub_log(self, name: str):
286 if self.fd is None:
287 raise ValueError('Попытка использовать закрытый файл журнала')
289 obj = self.__class__(file_base=self.logrotate_base,
290 file_obj=self.fd,
291 prefix=f'{self.prefix}/{name}',
292 time_to_flush=self.time_to_flush
295 obj.thread_safe_lock = self.thread_safe_lock
296 return obj