TG-PlatformPlus/logModel/logManager.py

216 lines
7.2 KiB
Python
Raw Permalink Normal View History

2026-03-02 14:29:58 +08:00
#!/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
2026-03-02 14:29:58 +08:00
class LogWorker(QObject):
logMsg = pyqtSignal(dict)
logMsgData = pyqtSignal(dict)
errorOccurred = pyqtSignal(str)
2026-03-02 14:29:58 +08:00
def __init__(self, localDateTime=None):
super().__init__()
self.running = False
self.logBuffer = queue.Queue(maxsize=2000)
2026-03-02 14:29:58 +08:00
self.localDateTime = localDateTime
self.endStr = "---END"
self.mutex = QMutex()
self.batch_size = 50
2026-03-02 14:29:58 +08:00
self._init_file_rotation()
def _init_file_rotation(self):
"""日志轮转配置"""
self.rotation_config = {
'max_size': 1024 * 1024 * 100, # 100MB
'backup_count': 30
}
def start(self):
"""主循环"""
2026-03-02 14:29:58 +08:00
self.running = True
batch_buffer = []
2026-03-02 14:29:58 +08:00
while self.running:
try:
# 批量收集
2026-03-02 14:29:58 +08:00
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)
2026-03-02 14:29:58 +08:00
if batch_buffer:
self._process_batch(batch_buffer)
batch_buffer.clear()
QThread.msleep(10)
2026-03-02 14:29:58 +08:00
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", "")
2026-03-02 14:29:58 +08:00
except KeyError as e:
self.errorOccurred.emit(f"缺失必要字段: {str(e)}")
def _process_batch(self, batch):
"""批量处理"""
locked = False
try:
2026-03-02 14:29:58 +08:00
self.mutex.lock()
locked = True
2026-03-02 14:29:58 +08:00
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)}")
2026-03-02 14:29:58 +08:00
finally:
if locked:
self.mutex.unlock()
2026-03-02 14:29:58 +08:00
def _batch_write(self, batch):
"""批量写入"""
2026-03-02 14:29:58 +08:00
log_path = projectManager.getLogPath()
if not log_path:
return
2026-03-02 14:29:58 +08:00
# 主日志文件
main_file = os.path.join(log_path, f"log{self.localDateTime}.txt")
2026-03-02 14:29:58 +08:00
self._write_to_file(main_file, batch)
# 标签日志
2026-03-02 14:29:58 +08:00
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")
2026-03-02 14:29:58 +08:00
tag_files.setdefault(tag_file, []).append(entry)
2026-03-02 14:29:58 +08:00
for file_path, entries in tag_files.items():
self._write_to_file(file_path, entries)
def _write_to_file(self, file_path, entries):
"""带轮转的写入"""
2026-03-02 14:29:58 +08:00
if os.path.exists(file_path) and os.path.getsize(file_path) > self.rotation_config['max_size']:
self._rotate_file(file_path)
2026-03-02 14:29:58 +08:00
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", ""),
2026-03-02 14:29:58 +08:00
"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)}")
2026-03-02 14:29:58 +08:00
def _handle_io_error(self, error):
"""IO错误处理"""
if hasattr(error, 'errno') and error.errno == 28:
2026-03-02 14:29:58 +08:00
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)}")
2026-03-02 14:29:58 +08:00
def appendLogFile(self, data):
"""线程安全的队列添加"""
try:
self.logBuffer.put_nowait(data)
2026-03-02 14:29:58 +08:00
except queue.Full:
pass
def stop(self):
"""安全停止"""
2026-03-02 14:29:58 +08:00
self.running = False
# 等待剩余日志处理完
wait_count = 0
while not self.logBuffer.empty() and wait_count < 50:
2026-03-02 14:29:58 +08:00
QThread.msleep(100)
wait_count += 1
2026-03-02 14:29:58 +08:00
class LogManager(QObject):
logMsg = pyqtSignal(dict)
logMsgData = pyqtSignal(dict)
proChange = pyqtSignal()
def __init__(self, parent=None):
2026-03-02 14:29:58 +08:00
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}")
2026-03-02 14:29:58 +08:00
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()