Rotary Logger  1.0.2
The middleware rotary logger
Loading...
Searching...
No Matches
rogger.py
Go to the documentation of this file.
1"""
2# +==== BEGIN rotary_logger =================+
3# LOGO:
4# ..........####...####..........
5# ......###.....#.#########......
6# ....##........#.###########....
7# ...#..........#.############...
8# ...#..........#.#####.######...
9# ..#.....##....#.###..#...####..
10# .#.....#.##...#.##..##########.
11# #.....##########....##...######
12# #.....#...##..#.##..####.######
13# .#...##....##.#.##..###..#####.
14# ..#.##......#.#.####...######..
15# ..#...........#.#############..
16# ..#...........#.#############..
17# ...##.........#.############...
18# ......#.......#.#########......
19# .......#......#.########.......
20# .........#####...#####.........
21# /STOP
22# PROJECT: rotary_logger
23# FILE: rogger.py
24# CREATION DATE: 18-03-2026
25# LAST Modified: 9:20:42 27-03-2026
26# DESCRIPTION:
27# A module that provides a universal python light on iops way of logging to files your program execution.
28# /STOP
29# COPYRIGHT: (c) Asperguide
30# PURPOSE: A class that is tailored to the module to allow it to log without fering of breaking structure.
31# // AR
32# +==== END rotary_logger =================+
33"""
34
35import sys
36import inspect
37from threading import RLock
38from datetime import datetime
39from io import TextIOWrapper
40from typing import Optional, TextIO, Union, TYPE_CHECKING
41from .constants import MODULE_NAME, LogToggle, RAW_STDOUT, RAW_STDERR
42
43if TYPE_CHECKING:
44 from .tee_stream import TeeStream
45
46
47class Rogger:
48 """
49 This is a custom made class that aims to work seamlessly with the library so that it doesn't break streams.
50 """
51
52 _class_lock: RLock = RLock()
53 _function_lock: RLock = RLock()
54 _instance: Optional["Rogger"] = None
55
56 def __new__(cls) -> "Rogger":
57 with cls._class_lock:
58 if cls._instance is None:
59 cls._instance = super().__new__(cls)
60 return cls._instance
61
63 self,
64 program_log: bool = False,
65 program_debug_log: bool = False,
66 suppress_program_warning_logs: bool = False,
67 suppress_program_error_logs: bool = False
68 ) -> None:
69 # the settings
70 self.toggles: LogToggle = self._create_log_toggle(
71 program_log,
72 program_debug_log,
73 suppress_program_warning_logs,
74 suppress_program_error_logs
75 )
76 # The shorthand strings used to build the log
77 self.program_name: str = f"{MODULE_NAME}"
78 self.success: str = "SUCCESS"
79 self.info: str = "INFO"
80 self.warning: str = "WARNING"
81 self.error: str = "ERROR"
82 self.critical: str = "CRITICAL"
83 self.debug: str = "DEBUG"
84
86 self,
87 program_log: bool = False,
88 program_debug_log: bool = False,
89 suppress_program_warning_logs: bool = False,
90 suppress_program_error_logs: bool = False
91 ) -> None:
92 """Re-create the toggle settings after the class has already been initialised.
93
94 Args:
95 program_log (bool, optional): Wether to log to the terminal. Defaults to False.
96 program_debug_log (bool, optional): Wether to log debug. Defaults to False.
97 suppress_program_warning_logs (bool, optional): Wether to display warnings or not. Defaults to False.
98 suppress_program_error_logs (bool, optional): Wether to display errors or not. Defaults to False.
99 """
100 new_toggle = self._create_log_toggle(
101 program_log,
102 program_debug_log,
103 suppress_program_warning_logs,
104 suppress_program_error_logs
105 )
106 with self._function_lock:
107 self.toggles = new_toggle
108
110 self,
111 program_log: bool = False,
112 program_debug_log: bool = False,
113 suppress_program_warning_logs: bool = False,
114 suppress_program_error_logs: bool = False
115 ) -> LogToggle:
116 """Define which log modes can be used.
117
118 Returns:
119 LogToggle: The dataclass to follow.
120 """
121 _success: bool = True
122 _info: bool = True
123 _warning: bool = True
124 _error: bool = True
125 _critical: bool = True
126 _debug: bool = True
127 if program_debug_log is False:
128 _debug = False
129 if program_log is False:
130 _success = False
131 _info = False
132 if suppress_program_warning_logs is True:
133 _warning = False
134 else:
135 _warning = True
136 if suppress_program_error_logs is True:
137 _error = False
138 _critical = False
139 else:
140 _error = True
141 _critical = True
142 return LogToggle(
143 program_log=program_log,
144 success=_success,
145 info=_info,
146 warning=_warning,
147 error=_error,
148 critical=_critical,
149 debug=_debug
150 )
151
152 def _get_date(self) -> str:
153 """
154 The function in charge of returning the date string for the line.
155
156 Returns:
157 str: the string ready to be embedded.
158 """
159 now = datetime.now()
160 final = now.strftime(
161 "%Y-%m-%d %H:%M:%S"
162 )
163 final += f",{now.microsecond // 1000:03d}"
164 return final
165
166 def _get_class_name(self, depth: int = 1) -> Optional[str]:
167 """Determine the name of the class that called the log
168
169 Args:
170 depth (int, optional): The upstream depth to look into. Defaults to 2.
171
172 Returns:
173 Optional[str]: The name of the class (if any)
174 """
175 if self.toggles.program_log is False:
176 return None
177
178 try:
179 frame = inspect.currentframe()
180 # type: ignore[reportBroadException] # pylint: disable=broad-exception-caught
181 except Exception:
182 return None
183 if frame is None:
184 return None
185
186 try:
187 current_depth = 0
188 while current_depth < depth:
189 if frame and frame.f_back is not None:
190 frame = frame.f_back
191 else:
192 frame = None
193 break
194 current_depth += 1
195 # type: ignore[reportBroadException] # pylint: disable=broad-exception-caught
196 except Exception:
197 return None
198
199 if frame is None:
200 return None
201 try:
202 locals_ = frame.f_locals
203 # type: ignore[reportBroadException] # pylint: disable=broad-exception-caught
204 except Exception:
205 return None
206
207 # Instance method
208 if "self" in locals_:
209 try:
210 return type(locals_["self"]).__name__
211 # type: ignore[reportBroadException] # pylint: disable=broad-exception-caught
212 except Exception:
213 return None
214
215 # Class method
216 if "cls" in locals_:
217 try:
218 return locals_["cls"].__name__
219 # type: ignore[reportBroadException] # pylint: disable=broad-exception-caught
220 except Exception:
221 return None
222
223 return None
224
225 def _get_function_name(self, depth: int = 2) -> Optional[str]:
226 """Determine the name of the function that called the log
227
228 Args:
229 depth (int, optional): The upstream depth to look into. Defaults to 2.
230
231 Returns:
232 Optional[str]: The name of the function (if any)
233 """
234 if self.toggles.program_log is False:
235 return None
236 try:
237 _func_name = inspect.currentframe()
238 # type: ignore[reportBroadException] # pylint: disable=broad-exception-caught
239 except Exception:
240 return None
241 if _func_name is None:
242 return None
243 current_depth = 0
244 try:
245 while current_depth < depth:
246 if _func_name and _func_name.f_back is not None:
247 _func_name = _func_name.f_back
248 else:
249 _func_name = None
250 break
251 current_depth += 1
252 # type: ignore[reportBroadException] # pylint: disable=broad-exception-caught
253 except Exception:
254 return None
255 if _func_name is None:
256 return None
257 try:
258 return _func_name.f_code.co_name
259 # type: ignore[reportBroadException] # pylint: disable=broad-exception-caught
260 except Exception:
261 return None
262
263 def _log_if_possible(self, log_type: str, message: str, function_name: Optional[str], class_name: Optional[str], stream: Union[Optional[TextIOWrapper], TextIO, "TeeStream"]) -> None:
264 """The generic function to log the message to the provided stream
265
266 Args:
267 log_type (str): The type of log message (Info, Warning, Error, etc)
268 message (str): The message provided by the user
269 function_name (Optional[str]): The name of the function
270 stream (Union[TextIO, TeeStream]): The stream to write to
271 """
272 if function_name is None:
273 function_name = self._get_function_name(3) or "Unknown"
274 if class_name is None:
275 class_name = self._get_class_name(2) or "Unknown"
276 date = self._get_date()
277 final_msg = f"[{date}] {self.program_name} {log_type} ({class_name}.{function_name}): {message}\n"
278 with self._function_lock:
279 if stream is None:
280 return
281 stream.write(final_msg)
282
283 def log_success(self, message: str, *, function_name: Optional[str] = None, class_name: Optional[str] = None, stream: Union[Optional[TextIOWrapper], TextIO, "TeeStream"] = RAW_STDOUT) -> None:
284 """Log a success message to the stream (if logging conditions are met)
285
286 Args:
287 message (str): The message to display
288 function_name (Optional[str], optional): The name of the function calling it. Defaults to None.
289 class_name (Optional[str], optional): The name of the class calling it. Defaults to None.
290 stream (Union[TextIO, TeeStream], optional): The stream to write to. Defaults to RAW_STDOUT.
291 """
292 if self.toggles.success is False:
293 return
294 if function_name is None:
295 function_name = self._get_function_name(2)
296 if class_name is None:
297 class_name = self._get_class_name(2)
298 self._log_if_possible(
299 log_type=self.success,
300 message=message,
301 function_name=function_name,
302 class_name=class_name,
303 stream=stream
304 )
305
306 def log_info(self, message: str, *, function_name: Optional[str] = None, class_name: Optional[str] = None, stream: Union[Optional[TextIOWrapper], TextIO, "TeeStream"] = RAW_STDOUT) -> None:
307 """Log a info message to the stream (if logging conditions are met)
308
309 Args:
310 message (str): The message to display
311 function_name (Optional[str], optional): The name of the function calling it. Defaults to None.
312 class_name (Optional[str], optional): The name of the class calling it. Defaults to None.
313 stream (Union[TextIO, TeeStream], optional): The stream to write to. Defaults to RAW_STDOUT.
314 """
315 if self.toggles.info is False:
316 return
317 if function_name is None:
318 function_name = self._get_function_name(2)
319 if class_name is None:
320 class_name = self._get_class_name(2)
321 self._log_if_possible(
322 log_type=self.success,
323 message=message,
324 function_name=function_name,
325 class_name=class_name,
326 stream=stream
327 )
328
329 def log_warning(self, message: str, *, function_name: Optional[str] = None, class_name: Optional[str] = None, stream: Union[Optional[TextIOWrapper], TextIO, "TeeStream"] = RAW_STDERR) -> None:
330 """Log a warning message to the stream (if logging conditions are met)
331
332 Args:
333 message (str): The message to display
334 function_name (Optional[str], optional): The name of the function calling it. Defaults to None.
335 class_name (Optional[str], optional): The name of the class calling it. Defaults to None.
336 stream (Union[TextIO, TeeStream], optional): The stream to write to. Defaults to RAW_STDERR.
337 """
338 if self.toggles.warning is False:
339 return
340 if function_name is None:
341 function_name = self._get_function_name(2)
342 if class_name is None:
343 class_name = self._get_class_name(2)
344 self._log_if_possible(
345 log_type=self.success,
346 message=message,
347 function_name=function_name,
348 class_name=class_name,
349 stream=stream
350 )
351
352 def log_error(self, message: str, *, function_name: Optional[str] = None, class_name: Optional[str] = None, stream: Union[Optional[TextIOWrapper], TextIO, "TeeStream"] = RAW_STDERR) -> None:
353 """Log an error message to the stream (if logging conditions are met)
354
355 Args:
356 message (str): The message to display
357 function_name (Optional[str], optional): The name of the function calling it. Defaults to None.
358 class_name (Optional[str], optional): The name of the class calling it. Defaults to None.
359 stream (Union[TextIO, TeeStream], optional): The stream to write to. Defaults to RAW_STDERR.
360 """
361 if self.toggles.error is False:
362 return
363 if function_name is None:
364 function_name = self._get_function_name(2)
365 if class_name is None:
366 class_name = self._get_class_name(2)
367 self._log_if_possible(
368 log_type=self.success,
369 message=message,
370 function_name=function_name,
371 class_name=class_name,
372 stream=stream
373 )
374
375 def log_critical(self, message: str, *, function_name: Optional[str] = None, class_name: Optional[str] = None, stream: Union[Optional[TextIOWrapper], TextIO, "TeeStream"] = RAW_STDERR) -> None:
376 """Log a critical message to the stream (if logging conditions are met)
377
378 Args:
379 message (str): The message to display
380 function_name (Optional[str], optional): The name of the function calling it. Defaults to None.
381 class_name (Optional[str], optional): The name of the class calling it. Defaults to None.
382 stream (Union[TextIO, TeeStream], optional): The stream to write to. Defaults to RAW_STDERR.
383 """
384 if self.toggles.critical is False:
385 return
386 if function_name is None:
387 function_name = self._get_function_name(2)
388 if class_name is None:
389 class_name = self._get_class_name(2)
390 self._log_if_possible(
391 log_type=self.success,
392 message=message,
393 function_name=function_name,
394 class_name=class_name,
395 stream=stream
396 )
397
398 def log_debug(self, message: str, *, function_name: Optional[str] = None, class_name: Optional[str] = None, stream: Union[Optional[TextIOWrapper], TextIO, "TeeStream"] = RAW_STDOUT) -> None:
399 """Log a debug message to the stream (if logging conditions are met)
400
401 Args:
402 message (str): The message to display
403 function_name (Optional[str], optional): The name of the function calling it. Defaults to None.
404 class_name (Optional[str], optional): The name of the class calling it. Defaults to None.
405 stream (Union[TextIO, TeeStream], optional): The stream to write to. Defaults to RAW_STDOUT.
406 """
407 if self.toggles.debug is False:
408 return
409 if function_name is None:
410 function_name = self._get_function_name(2)
411 if class_name is None:
412 class_name = self._get_class_name(2)
413 self._log_if_possible(
414 log_type=self.success,
415 message=message,
416 function_name=function_name,
417 class_name=class_name,
418 stream=stream
419 )
420
421
422# Module-level shared Rogger instance for convenience (used by TeeStream and others)
423RI: Rogger = Rogger()
None log_info(self, str message, *, Optional[str] function_name=None, Optional[str] class_name=None, Union[Optional[TextIOWrapper], TextIO, "TeeStream"] stream=RAW_STDOUT)
Definition rogger.py:306
None log_error(self, str message, *, Optional[str] function_name=None, Optional[str] class_name=None, Union[Optional[TextIOWrapper], TextIO, "TeeStream"] stream=RAW_STDERR)
Definition rogger.py:352
None re_toggle(self, bool program_log=False, bool program_debug_log=False, bool suppress_program_warning_logs=False, bool suppress_program_error_logs=False)
Definition rogger.py:91
None log_warning(self, str message, *, Optional[str] function_name=None, Optional[str] class_name=None, Union[Optional[TextIOWrapper], TextIO, "TeeStream"] stream=RAW_STDERR)
Definition rogger.py:329
None log_critical(self, str message, *, Optional[str] function_name=None, Optional[str] class_name=None, Union[Optional[TextIOWrapper], TextIO, "TeeStream"] stream=RAW_STDERR)
Definition rogger.py:375
None _log_if_possible(self, str log_type, str message, Optional[str] function_name, Optional[str] class_name, Union[Optional[TextIOWrapper], TextIO, "TeeStream"] stream)
Definition rogger.py:263
None log_debug(self, str message, *, Optional[str] function_name=None, Optional[str] class_name=None, Union[Optional[TextIOWrapper], TextIO, "TeeStream"] stream=RAW_STDOUT)
Definition rogger.py:398
LogToggle _create_log_toggle(self, bool program_log=False, bool program_debug_log=False, bool suppress_program_warning_logs=False, bool suppress_program_error_logs=False)
Definition rogger.py:115
Optional[str] _get_class_name(self, int depth=1)
Definition rogger.py:166
None __init__(self, bool program_log=False, bool program_debug_log=False, bool suppress_program_warning_logs=False, bool suppress_program_error_logs=False)
Definition rogger.py:68
None log_success(self, str message, *, Optional[str] function_name=None, Optional[str] class_name=None, Union[Optional[TextIOWrapper], TextIO, "TeeStream"] stream=RAW_STDOUT)
Definition rogger.py:283
Optional[str] _get_function_name(self, int depth=2)
Definition rogger.py:225
"Rogger" __new__(cls)
Definition rogger.py:56