#!/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) 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.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) 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.get("msg", "").replace("\n", "↵") except KeyError as e: self.errorOccurred.emit(f"缺失必要字段: {str(e)}") def _process_batch(self, batch): """批量处理""" locked = False try: self.mutex.lock() locked = True self._batch_write(batch) except PermissionError as e: self.errorOccurred.emit(f"文件权限错误: {str(e)}") except IOError as e: self._handle_io_error(e) except Exception as e: self.errorOccurred.emit(f"批量写入异常: {str(e)}") finally: if locked: 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: tag = entry.get("tag", "") if 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.get("time", ""), "level": entry.get("level", "DEBUG"), "msg": entry.get("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): """日志轮转""" try: 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)) except Exception as e: self.errorOccurred.emit(f"日志轮转失败: {str(e)}") def _handle_io_error(self, error): """IO错误处理""" if hasattr(error, 'errno') and error.errno == 28: self.errorOccurred.emit("磁盘空间不足,自动清除旧日志") self._cleanup_old_logs() else: self.errorOccurred.emit(f"IO错误: {str(error)}") def _cleanup_old_logs(self): """紧急空间清理""" try: log_path = projectManager.getLogPath() if not log_path: return 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)) except Exception as e: self.errorOccurred.emit(f"清理旧日志失败: {str(e)}") def appendLogFile(self, data): """线程安全的队列添加""" try: self.logBuffer.put_nowait(data) except queue.Full: pass def stop(self): """安全停止""" self.running = False # 等待剩余日志处理完 wait_count = 0 while not self.logBuffer.empty() and wait_count < 50: QThread.msleep(100) wait_count += 1 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(f"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()