#!/opt/homebrew/bin/python3 # -*- coding:utf-8 -*- import os import json import queue from PyQt6 import * from PyQt6.QtCore import * from logs import log from typing import Union from projectModel.projectManager import projectManager # class LogWorker(QObject): # logMsg = pyqtSignal(dict) # logMsgData = pyqtSignal(dict) # def __init__(self): # super().__init__() # self.running = False # self.logBuffer = queue.Queue() # self.endStr = "---END" # def start(self): # self.running = True # while self.running: # try: # if not self.logBuffer.empty(): # data = self.logBuffer.get() # data["time"] = QDateTime.currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz") # data["msg"] = data["msg"].replace("\n", " ") # self.appendLogFile(data) # self.logMsgData.emit(data) # else: # QThread.msleep(1) # except Exception as e: # log.info(f"Serial exception: {e}") # finally: # pass # def appendLogFile(self, data): # logPath = projectManager.getLogPath() # if logPath == "": # return # time = data["time"] # level = data["level"] # msg = data["msg"].replace("\n", "++") # color = data["color"] # tag = "tag" in data and data["tag"] or "" # log_data = { # "time": time, # "level": level, # "msg": msg, # "color": color, # "tag": tag # } # with open(os.path.join(logPath, f"log{QDateTime.currentDateTime().toString('yyyy-MM-dd')}.txt"), "a+",encoding='utf-8') as f: # json.dump(log_data, f, ensure_ascii=False) # f.write(f'{self.endStr}\n') # if tag != "": # with open(os.path.join(logPath, f"log-{tag}.txt"), "a+",encoding='utf-8') as f: # json.dump(log_data, f, ensure_ascii=False) # f.write(f'{self.endStr}\n') # def stop(self): # self.running = False class LogWorker(QObject): logMsg = pyqtSignal(dict) # 实时日志信号 logMsgData = pyqtSignal(dict) # 实时日志数据信号 errorOccurred = pyqtSignal(str) # 新增错误信号 def __init__(self, localDateTime=None): super().__init__() self.running = False self.logBuffer = queue.Queue(maxsize=2000) # 增加队列容量 #获取当前时间 self.localDateTime = localDateTime self.endStr = "---END" self.file_handles = {} # 文件句柄缓存 self.mutex = QMutex() # 线程安全锁 self.batch_size = 50 # 批量处理数量 self._init_file_rotation() def _init_file_rotation(self): """日志轮转配置""" self.rotation_config = { 'max_size': 1024 * 1024 * 100, # 100MB 'backup_count': 30 } def start(self): """优化后的主循环""" self.running = True batch_buffer = [] while self.running: try: # 批量收集模式 while not self.logBuffer.empty() and len(batch_buffer) < self.batch_size: data = self.logBuffer.get_nowait() self._preprocess_data(data) batch_buffer.append(data) self.logMsgData.emit(data) if batch_buffer: self._process_batch(batch_buffer) batch_buffer.clear() QThread.msleep(10) # 降低CPU占用 except Exception as e: self.errorOccurred.emit(f"日志处理异常: {str(e)}") QThread.msleep(1000) def _preprocess_data(self, data): """数据预处理""" try: # 统一时间格式 data["time"] = QDateTime.currentDateTime().toString("yyyy-MM-dd HH:mm:ss.zzz") # 保留原始换行符但转义显示 data["msg"] = data["msg"].replace("\n", "↵") except KeyError as e: self.errorOccurred.emit(f"缺失必要字段: {str(e)}") def _process_batch(self, batch): """批量处理逻辑""" try: # 文件写入 self.mutex.lock() self._batch_write(batch) self.mutex.unlock() except PermissionError as e: self.errorOccurred.emit(f"文件权限错误: {str(e)}") except IOError as e: self._handle_io_error(e) finally: self.mutex.unlock() def _batch_write(self, batch): """优化后的批量写入""" log_path = projectManager.getLogPath() if not log_path: return # 创建日期文件句柄 main_file = os.path.join(log_path, f"log_{self.localDateTime}.txt") # 写入主日志 self._write_to_file(main_file, batch) # 处理标签日志 tag_files = {} for entry in batch: if tag := entry.get("tag"): tag_file = os.path.join(log_path, f"log_{self.localDateTime}_{tag}.txt") tag_files.setdefault(tag_file, []).append(entry) # 批量写入标签日志 for file_path, entries in tag_files.items(): self._write_to_file(file_path, entries) def _write_to_file(self, file_path, entries): """带轮转的写入操作""" # 检查文件大小 if os.path.exists(file_path) and os.path.getsize(file_path) > self.rotation_config['max_size']: self._rotate_file(file_path) # 使用缓存的文件句柄 try: with open(file_path, "a+", encoding='utf-8') as f: for entry in entries: json.dump({ "time": entry["time"], "level": entry["level"], "msg": entry["msg"], "color": entry.get("color", "#000000"), "tag": entry.get("tag", "") }, f, ensure_ascii=False) f.write(f'{self.endStr}\n') except Exception as e: self.errorOccurred.emit(f"文件写入失败: {file_path} - {str(e)}") def _rotate_file(self, file_path): """日志轮转实现""" base_name = os.path.basename(file_path).split('.')[0] log_dir = os.path.dirname(file_path) # 删除最旧日志 existing_logs = sorted([f for f in os.listdir(log_dir) if f.startswith(base_name)]) if len(existing_logs) >= self.rotation_config['backup_count']: os.remove(os.path.join(log_dir, existing_logs[0])) # 重命名当前日志 timestamp = QDateTime.currentDateTime().toString("yyyyMMddHHmmss") new_name = f"{base_name}_{timestamp}.bak.txt" os.rename(file_path, os.path.join(log_dir, new_name)) def _handle_io_error(self, error): """IO错误处理""" if error.errno == 28: # No space left self.errorOccurred.emit("磁盘空间不足,自动清除旧日志") self._cleanup_old_logs() else: self.errorOccurred.emit(f"IO错误: {str(error)}") def _cleanup_old_logs(self): """紧急空间清理""" log_path = projectManager.getLogPath() all_files = sorted( [os.path.join(log_path, f) for f in os.listdir(log_path) if f.startswith("log_")], key=os.path.getctime ) while len(all_files) > 5: os.remove(all_files.pop(0)) def appendLogFile(self, data): """线程安全的队列添加""" try: if self.logBuffer.qsize() < 1900: # 保留100缓冲空间 self.logBuffer.put_nowait(data) else: self.errorOccurred.emit("日志队列已满,丢弃新条目") except queue.Full: pass def stop(self): """安全停止方法""" self.running = False # 处理剩余日志 while not self.logBuffer.empty(): QThread.msleep(100) # 关闭所有文件句柄 self.file_handles.clear() class LogManager(QObject): logMsg = pyqtSignal(dict) logMsgData = pyqtSignal(dict) proChange = pyqtSignal() def __init__(self, parent = None): super().__init__() self.thread = QThread() self.localDateTime = QDateTime.currentDateTime().toString('yyyy-MM-dd_HH-mm-ss') self.logWorker = LogWorker(self.localDateTime) self.logWorker.logMsgData.connect(self.onLogMsgData) self.logWorker.moveToThread(self.thread) self.thread.started.connect(self.logWorker.start) self.thread.start() def addLogMsg(self, data): try: self.logWorker.logBuffer.put(data) except Exception as e: log.error("addLogMsg Error",e) def onLogMsgData(self, msgData): self.logMsgData.emit(msgData) def getInfo(self, proId): logPath = projectManager.getLogPath() logFilePath = os.path.join(logPath, f"log{self.localDateTime}.txt") return logFilePath def loadProLog(self, proId): logPath = projectManager.getLogPath() logFilePath = os.path.join(logPath, f"log{self.localDateTime}.txt") return logFilePath logManager = LogManager()