184 lines
5.3 KiB
Python
184 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
日志记录模块
|
||
OOP设计的日志记录器
|
||
"""
|
||
|
||
import json
|
||
from datetime import datetime
|
||
from typing import List, Dict, Any, Optional
|
||
from pathlib import Path
|
||
from dataclasses import dataclass
|
||
|
||
|
||
@dataclass
|
||
class LogEvent:
|
||
"""日志事件数据类"""
|
||
timestamp: str
|
||
event_type: str
|
||
message: str
|
||
data: Dict[str, Any]
|
||
|
||
def to_dict(self) -> Dict[str, Any]:
|
||
"""转换为字典"""
|
||
return {
|
||
'timestamp': self.timestamp,
|
||
'type': self.event_type,
|
||
'message': self.message,
|
||
'data': self.data
|
||
}
|
||
|
||
|
||
class Logger:
|
||
"""日志记录器类"""
|
||
|
||
def __init__(self, log_dir: str = "logs", max_log_files: int = 10):
|
||
"""
|
||
初始化日志记录器
|
||
|
||
Args:
|
||
log_dir: 日志目录
|
||
max_log_files: 最大日志文件数
|
||
"""
|
||
self.log_dir = Path(log_dir)
|
||
self.max_log_files = max_log_files
|
||
self.events: List[LogEvent] = []
|
||
self.session_start = datetime.now()
|
||
|
||
self._init_directory()
|
||
self._cleanup_old_logs()
|
||
|
||
def _init_directory(self) -> None:
|
||
"""初始化日志目录"""
|
||
self.log_dir.mkdir(parents=True, exist_ok=True)
|
||
|
||
def _cleanup_old_logs(self) -> None:
|
||
"""清理旧日志文件"""
|
||
log_files = sorted(
|
||
self.log_dir.glob("network_monitor_*.log"),
|
||
key=lambda x: x.stat().st_mtime,
|
||
reverse=True
|
||
)
|
||
|
||
for old_log in log_files[self.max_log_files:]:
|
||
old_log.unlink()
|
||
|
||
def _generate_filename(self) -> str:
|
||
"""生成日志文件名"""
|
||
timestamp = self.session_start.strftime("%Y%m%d_%H%M%S")
|
||
return f"network_monitor_{timestamp}.log"
|
||
|
||
def log(self, event_type: str, message: str, **data) -> None:
|
||
"""
|
||
记录日志事件
|
||
|
||
Args:
|
||
event_type: 事件类型
|
||
message: 事件消息
|
||
**data: 附加数据
|
||
"""
|
||
event = LogEvent(
|
||
timestamp=datetime.now().isoformat(),
|
||
event_type=event_type,
|
||
message=message,
|
||
data=data
|
||
)
|
||
self.events.append(event)
|
||
|
||
def log_ping(self, success: bool, latency: float, error: Optional[str] = None) -> None:
|
||
"""
|
||
记录ping事件
|
||
|
||
Args:
|
||
success: 是否成功
|
||
latency: 延迟(毫秒)
|
||
error: 错误信息
|
||
"""
|
||
self.log(
|
||
"ping",
|
||
"Ping执行成功" if success else "Ping执行失败",
|
||
success=success,
|
||
latency_ms=latency,
|
||
error=error
|
||
)
|
||
|
||
def log_status_change(self, status: str, duration: Optional[float] = None) -> None:
|
||
"""
|
||
记录状态变化
|
||
|
||
Args:
|
||
status: 状态(connected/disconnected)
|
||
duration: 持续时间
|
||
"""
|
||
message = f"网络状态变化: {status}"
|
||
if duration is not None:
|
||
message += f" (持续{duration:.1f}秒)"
|
||
|
||
self.log("status_change", message, status=status, duration_seconds=duration)
|
||
|
||
def log_error(self, error_type: str, error_message: str) -> None:
|
||
"""
|
||
记录错误
|
||
|
||
Args:
|
||
error_type: 错误类型
|
||
error_message: 错误消息
|
||
"""
|
||
self.log("error", f"{error_type}: {error_message}", error_type=error_type)
|
||
|
||
def log_shutdown(self, reason: str) -> None:
|
||
"""
|
||
记录关机事件
|
||
|
||
Args:
|
||
reason: 关机原因
|
||
"""
|
||
self.log("shutdown", f"系统关机: {reason}", reason=reason)
|
||
|
||
def save(self) -> str:
|
||
"""
|
||
保存日志到文件
|
||
|
||
Returns:
|
||
日志文件路径
|
||
"""
|
||
log_file = self.log_dir / self._generate_filename()
|
||
|
||
session = {
|
||
'session_start': self.session_start.isoformat(),
|
||
'session_end': datetime.now().isoformat(),
|
||
'total_events': len(self.events),
|
||
'events': [event.to_dict() for event in self.events]
|
||
}
|
||
|
||
with open(log_file, 'w', encoding='utf-8') as f:
|
||
json.dump(session, f, ensure_ascii=False, indent=2)
|
||
|
||
return str(log_file)
|
||
|
||
def get_statistics(self) -> Dict[str, Any]:
|
||
"""
|
||
获取统计信息
|
||
|
||
Returns:
|
||
统计信息字典
|
||
"""
|
||
ping_events = [e for e in self.events if e.event_type == "ping"]
|
||
successful_pings = [e for e in ping_events if e.data.get("success", False)]
|
||
|
||
return {
|
||
'total_events': len(self.events),
|
||
'total_pings': len(ping_events),
|
||
'successful_pings': len(successful_pings),
|
||
'failed_pings': len(ping_events) - len(successful_pings),
|
||
'success_rate': self._calculate_success_rate(ping_events, successful_pings),
|
||
'session_duration': (datetime.now() - self.session_start).total_seconds()
|
||
}
|
||
|
||
def _calculate_success_rate(self, all_pings: List[LogEvent], successful: List[LogEvent]) -> str:
|
||
"""计算成功率"""
|
||
if not all_pings:
|
||
return "0%"
|
||
return f"{(len(successful) / len(all_pings) * 100):.2f}%"
|