CelestialSurveyor
168 строк · 5.0 Кб
1import datetime2import inspect3import logging4import os5import sys6import threading7import wx.lib.newevent8
9from functools import wraps10from multiprocessing import Queue11from logging.handlers import QueueListener12from typing import Callable13
14# Define a custom event for logging
15LogEvent, EVT_LOG_EVENT = wx.lib.newevent.NewEvent()16
17
18def get_function_and_class_name(func: Callable) -> str:19"""20Get the function and class name of the provided function.
21
22Args:
23func: The function to extract the name from.
24
25Returns:
26str: The combined function and class name if applicable.
27"""
28# Check if the provided object is a function29function_name = func.__name__30if inspect.ismethod(func):31class_name = func.__self__.__class__.__name__32res = f"{class_name}.{function_name}"33else:34res = function_name35return res36
37
38def arg_logger(func: Callable) -> Callable:39"""40Decorator for logging function arguments and return values.
41
42Args:
43func: The function to decorate.
44
45Returns:
46Callable: The decorated function.
47"""
48@wraps(func)49def new_func(*args, **kwargs):50saved_args = locals()51logger = get_logger()52func_name = get_function_and_class_name(func)53
54logger.log.debug(f"{func_name}({saved_args})")55return func(*args, **kwargs)56return new_func57
58
59class LogHandler(logging.Handler):60"""61Custom logging handler that appends log messages to a wx.TextCtrl widget.
62
63Args:
64text_ctrl: The wx.TextCtrl widget to append log messages to.
65"""
66def __init__(self, text_ctrl):67logging.Handler.__init__(self)68self._text_ctrl = text_ctrl69
70@property71def text_ctrl(self):72return self._text_ctrl73
74@text_ctrl.setter75def text_ctrl(self, value):76self._text_ctrl = value77
78def emit(self, record: logging.LogRecord) -> None:79"""80Append log message to the text control.
81"""
82msg = self.format(record)83if self.text_ctrl is not None:84wx.CallAfter(self.text_ctrl.AppendText, msg + '\n') # Append log message to the text control85
86
87class LogFileHandler(logging.FileHandler):88"""89Custom logging handler that logs into file.
90
91"""
92def __init__(self):93fp = self.__get_log_file_path()94super(LogFileHandler, self).__init__(fp)95
96@staticmethod97def __get_log_file_path() -> str:98"""99Generates the log file path based on the current timestamp.
100
101Returns:
102str: The log file path.
103"""
104folder = os.path.join(sys.path[1], "log")105os.makedirs(folder, exist_ok=True)106current_time = datetime.datetime.now()107file_name = "{}.log".format(current_time.strftime("%y%m%d%H%M"))108fp = os.path.join(folder, file_name)109return fp110
111
112class Logger:113"""114Custom logger class that manages logging messages. Singleton class.
115"""
116
117log_level = logging.INFO118_instance_lock = threading.Lock()119formatter = None120
121def __new__(cls, text_ctrl=None):122with cls._instance_lock:123if not hasattr(cls, '_instance'):124cls._instance = super(Logger, cls).__new__(cls)125cls._instance.log = logging.getLogger('MyLogger')126cls._instance.log.setLevel(cls.log_level)127if cls.log_level == logging.DEBUG:128cls.formatter = logging.Formatter(129'[%(asctime)s] p%(process)s {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s',130'%m-%d %H:%M:%S')131else:132cls.formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')133
134gui_handler = LogHandler(text_ctrl)135gui_handler.setFormatter(cls.formatter)136cls._instance.log.addHandler(gui_handler)137console_handler = logging.StreamHandler()138console_handler.setLevel(cls.log_level)139console_handler.setFormatter(cls.formatter)140cls._instance.log.addHandler(console_handler)141file_handler = LogFileHandler()142file_handler.setLevel(cls.log_level)143file_handler.setFormatter(cls.formatter)144cls._instance.log.addHandler(file_handler)145cls._instance.text_ctrl = text_ctrl146
147return cls._instance148
149@property150def text_ctrl(self):151return self.log.handlers[0].text_ctrl152
153@text_ctrl.setter154def text_ctrl(self, value):155self.log.handlers[0].text_ctrl = value156
157def start_process_listener(self, queue: Queue) -> None:158"""Start listening for log messages from the given queue. Used to get logs from child processes."""159self.listener = QueueListener(queue, *self.log.handlers)160self.listener.start()161
162def stop_process_listener(self) -> None:163"""Stop listening for log messages from the queue. Used to stop getting logs from child processes."""164self.listener.stop()165
166
167def get_logger() -> Logger:168return Logger()169