154 lines
4.7 KiB
Python
154 lines
4.7 KiB
Python
"""
|
|
Log Manager - 持久化日志管理器
|
|
支持日志加载、保存、追加、清空
|
|
"""
|
|
import json
|
|
import os
|
|
import threading
|
|
from datetime import datetime
|
|
from typing import List, Dict, Optional
|
|
from app import config
|
|
|
|
|
|
class LogManager:
|
|
"""日志管理器 - 线程安全"""
|
|
|
|
def __init__(self, log_file: str = None, max_logs: int = None):
|
|
self.log_file = log_file or config.LOG_FILE
|
|
self.max_logs = max_logs or config.MAX_LOGS
|
|
self._lock = threading.Lock()
|
|
self.logs: List[Dict] = []
|
|
self._load_logs()
|
|
|
|
def _load_logs(self):
|
|
"""启动时加载历史日志"""
|
|
with self._lock:
|
|
if os.path.exists(self.log_file):
|
|
try:
|
|
with open(self.log_file, 'r', encoding='utf-8') as f:
|
|
data = json.load(f)
|
|
self.logs = data.get('logs', [])
|
|
except (json.JSONDecodeError, IOError) as e:
|
|
print(f"[LogManager] 加载日志文件失败: {e}")
|
|
self.logs = []
|
|
else:
|
|
self.logs = []
|
|
|
|
def append(self, level: str, message: str, thread_id: Optional[str] = None) -> Dict:
|
|
"""
|
|
追加新日志
|
|
返回日志条目
|
|
"""
|
|
with self._lock:
|
|
log_entry = {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"level": level,
|
|
"message": message,
|
|
"thread_id": thread_id
|
|
}
|
|
self.logs.append(log_entry)
|
|
|
|
# 限制日志数量(保留最新的 max_logs 条)
|
|
if len(self.logs) > self.max_logs:
|
|
self.logs = self.logs[-self.max_logs:]
|
|
|
|
# 异步保存到文件
|
|
self._save()
|
|
|
|
return log_entry
|
|
|
|
def _save(self):
|
|
"""保存到文件(需在锁内调用)"""
|
|
try:
|
|
with open(self.log_file, 'w', encoding='utf-8') as f:
|
|
json.dump({
|
|
"logs": self.logs,
|
|
"last_updated": datetime.now().isoformat()
|
|
}, f, ensure_ascii=False, indent=2)
|
|
except IOError as e:
|
|
print(f"[LogManager] 保存日志文件失败: {e}")
|
|
|
|
def get_logs(self, level: Optional[str] = None, limit: Optional[int] = None) -> List[Dict]:
|
|
"""
|
|
获取日志
|
|
:param level: 过滤级别 (None/INFO/SUCCESS/WARNING/ERROR/DEBUG)
|
|
:param limit: 限制返回数量
|
|
"""
|
|
with self._lock:
|
|
if level:
|
|
filtered = [log for log in self.logs if log['level'] == level]
|
|
else:
|
|
filtered = self.logs
|
|
|
|
if limit:
|
|
return filtered[-limit:]
|
|
return filtered
|
|
|
|
def search_logs(self, keyword: str, limit: Optional[int] = None) -> List[Dict]:
|
|
"""
|
|
搜索日志
|
|
:param keyword: 搜索关键词
|
|
:param limit: 限制返回数量
|
|
"""
|
|
with self._lock:
|
|
filtered = [
|
|
log for log in self.logs
|
|
if keyword.lower() in log['message'].lower()
|
|
]
|
|
|
|
if limit:
|
|
return filtered[-limit:]
|
|
return filtered
|
|
|
|
def clear(self):
|
|
"""清空日志"""
|
|
with self._lock:
|
|
self.logs = []
|
|
self._save()
|
|
|
|
def get_stats(self) -> Dict:
|
|
"""获取日志统计信息"""
|
|
with self._lock:
|
|
stats = {
|
|
"total": len(self.logs),
|
|
"by_level": {}
|
|
}
|
|
|
|
for log in self.logs:
|
|
level = log['level']
|
|
stats["by_level"][level] = stats["by_level"].get(level, 0) + 1
|
|
|
|
return stats
|
|
|
|
def export_to_text(self) -> str:
|
|
"""导出为文本格式"""
|
|
with self._lock:
|
|
lines = []
|
|
lines.append("=" * 80)
|
|
lines.append("小红书采集器日志")
|
|
lines.append(f"导出时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
lines.append(f"总日志数: {len(self.logs)}")
|
|
lines.append("=" * 80)
|
|
lines.append("")
|
|
|
|
for log in self.logs:
|
|
timestamp = log['timestamp'][:19] # 去掉微秒
|
|
level = log['level'].ljust(8)
|
|
thread = f"[{log['thread_id']}]" if log.get('thread_id') else ""
|
|
message = log['message']
|
|
lines.append(f"{timestamp} | {level} | {thread} {message}")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
# 全局日志管理器实例
|
|
_log_manager_instance = None
|
|
|
|
|
|
def get_log_manager() -> LogManager:
|
|
"""获取全局日志管理器实例"""
|
|
global _log_manager_instance
|
|
if _log_manager_instance is None:
|
|
_log_manager_instance = LogManager()
|
|
return _log_manager_instance
|