184 lines
5.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""数据模型定义。
依据 docs/01-design/database-design.md 中 fault_log / root_cause /
fault_type / fault_filter_rule / event 表结构,提供进程内数据模型。
核心实现零第三方依赖,全部使用标准库 dataclass。
"""
from __future__ import annotations
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
def _now() -> datetime:
return datetime.now(timezone.utc)
def _to_dt(value: Any) -> datetime:
"""将字符串/时间戳转换为带时区的 datetime。
支持 ISO8601带 Z 或空格分隔、Unix 时间戳以及 syslog 风格
``Jan 1 10:18:00``(无年份,缺省使用当前年份)。
"""
if value is None:
return _now()
if isinstance(value, datetime):
if value.tzinfo is None:
return value.replace(tzinfo=timezone.utc)
return value
if isinstance(value, (int, float)):
return datetime.fromtimestamp(value, tz=timezone.utc)
text = str(value).strip()
if text.endswith("Z"):
text = text[:-1] + "+00:00"
dt: Optional[datetime] = None
try:
dt = datetime.fromisoformat(text)
except ValueError:
try:
dt = datetime.fromisoformat(text.replace(" ", "T"))
except ValueError:
# syslog 风格Jan 1 10:18:00无年份缺省当前年份
normalized = " ".join(text.split())
dt = datetime.strptime(normalized, "%b %d %H:%M:%S")
dt = dt.replace(year=datetime.now().year)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
return dt
@dataclass
class FaultLog:
"""故障日志(归类后),对应 fault_log 表。"""
fault_log_id: str
host_id: str
message: str
level: str = "ERROR"
fingerprint: str = ""
fault_type: Optional[str] = None
cluster_id: Optional[str] = None
service: Optional[str] = None
trace_id: Optional[str] = None
occurred_at: datetime = field(default_factory=_now)
count: int = 1
def to_dict(self) -> Dict[str, Any]:
data = asdict(self)
data["occurred_at"] = self.occurred_at.isoformat()
return data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "FaultLog":
payload = dict(data)
payload["occurred_at"] = _to_dt(payload.get("occurred_at"))
return cls(**{k: v for k, v in payload.items() if k in cls.__dataclass_fields__})
@dataclass
class RootCause:
"""根因结论,对应 root_cause 表。"""
fault_log_id: str
cause_type: str
evidence: List[Dict[str, Any]] = field(default_factory=list)
confidence: float = 0.0
analysis_at: datetime = field(default_factory=_now)
def to_dict(self) -> Dict[str, Any]:
data = asdict(self)
data["analysis_at"] = self.analysis_at.isoformat()
return data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "RootCause":
payload = dict(data)
payload["analysis_at"] = _to_dt(payload.get("analysis_at"))
return cls(**{k: v for k, v in payload.items() if k in cls.__dataclass_fields__})
@dataclass
class FaultType:
"""故障类型,对应 fault_type 表。"""
fault_type: str
name: str
description: str = ""
pattern: str = ""
severity: str = "warning"
enabled: bool = True
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "FaultType":
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
@dataclass
class FaultFilterRule:
"""故障日志过滤规则,对应 fault_filter_rule 表。"""
name: str
level: str = "ERROR"
pattern: str = ""
exclude_pattern: str = ""
enabled: bool = True
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "FaultFilterRule":
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
@dataclass
class Event:
"""检测事件(用于根因分析的关联输入),对应 event 表。"""
event_id: str
host_id: str
metric: str
value: float
threshold: float
operator: str = "gt"
status: str = "firing"
severity: str = "warning"
fired_at: datetime = field(default_factory=_now)
def to_dict(self) -> Dict[str, Any]:
data = asdict(self)
data["fired_at"] = self.fired_at.isoformat()
return data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Event":
payload = dict(data)
payload["fired_at"] = _to_dt(payload.get("fired_at"))
return cls(**{k: v for k, v in payload.items() if k in cls.__dataclass_fields__})
@dataclass
class ParsedLog:
"""解析后的结构化日志条目(捕获管道内部表示)。"""
timestamp: datetime = field(default_factory=_now)
host_id: str = ""
service: str = ""
level: str = "INFO"
message: str = ""
trace_id: Optional[str] = None
raw: str = ""
def to_dict(self) -> Dict[str, Any]:
data = asdict(self)
data["timestamp"] = self.timestamp.isoformat()
return data