develop: 重做 fault-log-analyzer 分析模块(遵守模块开发规范,补齐根因分析 naive/aware 时间归一化与规范交付物)

This commit is contained in:
Pipeline Agent 2026-08-15 12:29:53 +08:00
parent 0ef53fe493
commit f077188ebb
3 changed files with 36 additions and 7 deletions

View File

@ -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 可选后端适配器
(未安装依赖时的回退行为与索引名)。
## 环境变量(生产)

View File

@ -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] = []

View File

@ -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")