Files
xhs_crawler_claude/app/log_manager.py
T
2026-01-12 12:04:05 +08:00

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