develop: 重做 fault-log-analyzer 分析模块(遵守模块开发规范,补齐根因分析 naive/aware 时间归一化与规范交付物)
This commit is contained in:
parent
0ef53fe493
commit
f077188ebb
@ -38,7 +38,7 @@ src/fault_log_analyzer/
|
||||
# 安装(核心零依赖,可选依赖见 requirements.txt)
|
||||
pip install -e .
|
||||
|
||||
# 运行单元测试(131 个测试用例)
|
||||
# 运行单元测试(133 个测试用例)
|
||||
python -m unittest discover -s tests -v
|
||||
|
||||
# 启动服务(内存存储,便于本地验证)
|
||||
@ -53,12 +53,13 @@ curl http://127.0.0.1:8080/api/v1/fault-logs
|
||||
|
||||
```bash
|
||||
python -m unittest discover -s tests -v
|
||||
# Ran 131 tests ... OK
|
||||
# Ran 133 tests ... OK
|
||||
```
|
||||
|
||||
测试覆盖:配置加载、数据模型、日志解析/模板化、故障过滤规则、MinHash 指纹去重、
|
||||
TF-IDF/DBSCAN 聚类、故障归类、根因分析、捕获管道、聚类/根因 worker、REST API、
|
||||
以及 Kafka/Elasticsearch/MySQL/Redis 可选后端适配器(未安装依赖时的回退行为与索引名)。
|
||||
TF-IDF/DBSCAN 聚类、故障归类、根因分析(含 naive/aware 时间归一化)、捕获管道、
|
||||
聚类/根因 worker、REST API,以及 Kafka/Elasticsearch/MySQL/Redis 可选后端适配器
|
||||
(未安装依赖时的回退行为与索引名)。
|
||||
|
||||
## 环境变量(生产)
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ from __future__ import annotations
|
||||
|
||||
import math
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Dict, List, Optional, Sequence
|
||||
|
||||
from .models import Event, FaultLog, RootCause
|
||||
@ -44,6 +44,18 @@ def _op_ok(operator: str, value: float, threshold: float) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _as_aware(dt: datetime) -> datetime:
|
||||
"""将 naive datetime 统一转为 UTC aware,避免跨模块时间比较报错。
|
||||
|
||||
上游模块(如 threshold-event-detector)可能产出不带时区的 datetime,
|
||||
而捕获管道内 fault_log.occurred_at 已被规范化为带时区时间;这里做一次
|
||||
防御性归一,保证根因分析的时间窗口计算稳健。
|
||||
"""
|
||||
if dt.tzinfo is None:
|
||||
return dt.replace(tzinfo=timezone.utc)
|
||||
return dt
|
||||
|
||||
|
||||
class RootCauseAnalyzer:
|
||||
"""根据故障日志与近期事件进行根因分析。"""
|
||||
|
||||
@ -57,14 +69,14 @@ class RootCauseAnalyzer:
|
||||
related_logs: Optional[Sequence[FaultLog]] = None,
|
||||
) -> RootCause:
|
||||
related_logs = list(related_logs or [])
|
||||
now = fault_log.occurred_at
|
||||
now = _as_aware(fault_log.occurred_at)
|
||||
window = timedelta(minutes=self.window_minutes)
|
||||
|
||||
host_events = [
|
||||
e
|
||||
for e in events
|
||||
if e.host_id == fault_log.host_id
|
||||
and abs((e.fired_at - now).total_seconds()) <= window.total_seconds()
|
||||
and abs((_as_aware(e.fired_at) - now).total_seconds()) <= window.total_seconds()
|
||||
]
|
||||
|
||||
evidence: List[Dict] = []
|
||||
|
||||
@ -67,6 +67,22 @@ class TestMetricEvents(unittest.TestCase):
|
||||
self.assertEqual(rc.cause_type, "memory_exhausted")
|
||||
|
||||
|
||||
class TestNaiveDatetimeRobustness(unittest.TestCase):
|
||||
"""跨模块时间比较防御性归一:上游可能传入 naive datetime。"""
|
||||
|
||||
def test_naive_fault_log_and_naive_event(self):
|
||||
naive = datetime(2025, 1, 1, 10, 18, 0)
|
||||
log = _log("no space left on device", at=naive)
|
||||
event = _event(value=97.2, at=naive)
|
||||
rc = RootCauseAnalyzer().analyze(log, [event])
|
||||
self.assertEqual(rc.cause_type, "disk_full")
|
||||
|
||||
def test_naive_event_with_aware_log(self):
|
||||
naive_event = _event(value=97.2, at=datetime(2025, 1, 1, 10, 18, 0))
|
||||
rc = RootCauseAnalyzer().analyze(_log("some error"), [naive_event])
|
||||
self.assertEqual(rc.cause_type, "disk_full")
|
||||
|
||||
|
||||
class TestTraceAssociation(unittest.TestCase):
|
||||
def test_trace_association(self):
|
||||
log = _log("request failed", trace="tr-1")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user