Abhinav0905/Incidentlens
GitHub: Abhinav0905/Incidentlens
IncidentLens 从日志、架构元数据和部署事件中自动重建分布式系统故障,生成带有证据溯源的 3D 可视化回放和结构化分析报告。
Stars: 1 | Forks: 0
# IncidentLens
为分布式系统提供有据可查的故障复盘。
IncidentLens 会读取架构元数据、日志、指标和部署事件,然后重建故障期间发生的一切:哪个服务最先发生故障、在此之前发生了什么变更、故障是如何在依赖图中蔓延的,以及它对客户造成了什么影响。最终呈现的是一段电影般的 3D 故障回放——在您的架构上空推进的连续镜头,伴随着逐帧旁白——外加一份值班工程师可直接执行的简报。只需将其指向一个代码库及其日志文件,它就能在真实故障发生时自动完成这一切。
要在演示或黑客松上展示该项目?请使用这份
[三分钟运行手册](HACKATHON_RUNBOOK.md)。
## 本项目的核心理念
**推测出的根本原因绝不会被当作事实呈现。**
生产环境故障往往错综复杂。日志不完整、时钟漂移、多个问题同时爆发。因此,IncidentLens 产生的每一个结论都附带状态和置信度得分,且每一项断言都可追溯到支持该断言的证据 ID:
- `confirmed` — 遥测数据直接显示其发生过
- `inferred` — 证据指向这一结论,但应由人工进行验证
- `unknown` — 确实存在这种可能性,但现有遥测数据无法得出定论
分析过程还会列出它*无法*看到的内容:未上报遥测数据的服务、缺失的审计日志、空缺的变更历史。让系统知道自己“不知道什么”,正是本工具的核心意义所在。
## 快速开始
可在 macOS、Linux 或 Windows(通过 WSL2)上运行。IncidentLens 核心和 Studio
支持 Python 3.10+;可选的 Genblaze 集成需要 Python 3.11+。
```
git clone https://github.com/Abhinav0905/Incidentlens.git
cd Incidentlens
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
incidentlens serve
```
打开 http://127.0.0.1:8000,选择一个场景,点击 **Reconstruct incident**。或者使用 Docker:
```
docker compose up --build
```
## 产出内容
对于每次故障,引擎都会生成一份单一的 `IncidentAnalysis` 文档:
- 一条从健康状态到故障,再到(如果观测到)恢复的时间线
- 带有状态、置信度和支持性证据 ID 的排序后假设
- 传播链:哪个服务导致了哪个服务降级,以及通过何种机制
- 客户影响(仅基于直接证据陈述)
- 缺失的证据——响应人员应优先填补的空白
- 推荐的检查和修复措施,按优先级排序,并附有明确的风险说明
- 工程师简报和高管摘要
- 驱动 UI 中动画架构视图的回放脚本
UI 会像播放录像一样重现故障:随着时间线的推进,节点会从健康变为警告再到严重;当故障穿过依赖边时,传播路径会亮起;此外,字幕会将每一帧与其证据关联起来。
## 内置场景
代码库附带了七个合成的故障场景。它们的存在是为了让您看到引擎
如何针对完全不同的故障进行推理——相同的代码,没有针对特定场景的分支。
### 同一应用,四种故障
其中四个场景在*相同*的服务图和*相同*的 16 模块代码图上运行:
`hary-platform`,一个 AI 助手。只需了解一次架构,然后观察每次其不同
部分的故障。其核心在于,分析过程并未针对特定故障进行调优——
改变的仅仅是遥测数据。
| 场景 | 故障内容 | 引擎定位 | 深度 |
| --- | --- | --- | --- |
| `rate-limit-exhaustion` | 流量激增导致 Redis 连接池耗尽;限流器触发失败关闭(fail closed),每个请求在到达图之前就被以 `429` 拒绝 | `middleware.rate_limit` | 前门 |
| `pii-guardrail-crash` | 异常的 unicode 序列导致 PII 脱敏崩溃。格式良好的请求继续工作,因此只有*部分*流量中断 | `hary.guardrails.pii._get_presidio` | 流水线中部 |
| `gateway-auth-rejection` | 新配置的虚拟密钥在两种身份验证方案下均被拒绝(返回 `401`);断路器打开,助手面板变暗 | `hary.models.llm_factory.get_llm_for_tier` | LLM 客户端 |
| `agentic-retry-exhaustion` | prompt-pack 的变更导致 Agent 误将 tool-call payload 视为可重试。它耗尽了重试预算,截断了部分答案进行补救,并返回 **HTTP 200** | `hary.graph.nodes.agent.AgentNode.__call__` | agentic 节点 |
最后一种情况很有意思:没有任何 5xx 错误,所有可用性仪表板
保持全绿。HTTP 成功率保持在 99.6%,但部分答案补救的比例从 0.3%
攀升至 44.8%,p95 延迟翻了三倍,点踩量从每小时 3 次增加到每小时 41 次。这种隐蔽的质量倒退,是日志尾部无法向您展示的故障。
`rate-limit-exhaustion` 被定位到一个模块而非函数,因为
`middleware.rate_limit` 在内置的代码图中没有对应的符号。这是刻意为之:
工具会退化为模块级别定位,而不是凭空捏造一个它无法支持的 stack frame。
### 另外三种架构
为了证明分析过程未针对单一系统进行调优,我们还提供了:`cache-stampede`(冷缓存导致请求涌入索引层,直到数据库饱和,随后自行恢复——不涉及部署)、`checkout-secret-rotation`(部署重新指向数据库密钥,导致订单 worker 积压),以及 `queue-poison-message`(schema 变更产生了一条无法解码的记录,消费者陷入崩溃循环)。
您可以运行其中任何一个。由于遥测数据不同,输出结果也会有所不同——这就是
我们要展示的效果。
## 实时观察您自己的系统
实时循环不需要代理,也不需要修改代码——一个日志文件就是全部的集成接口:
```
pip install -e ".[studio]" # plus ffmpeg
incidentlens discover . # scan the repo -> architecture proposal + config
incidentlens watch --config incidentlens.config.json
```
`discover` 从您的代码检出中提取服务图(包括 docker-compose、服务目录、配置交叉引用、在设置中发现的外部网关——这两个文件都是可编辑的建议方案)。它还会使用 `ast` 扫描每个 Python 服务的*内部*——LangGraph 节点连线、FastAPI 入口点、middleware 链、共享的 LLM/client 模块——以便随后视频可以深入到发生故障的服务内部,并追踪请求在各个内部阶段的流转。随后,`watch` 会持续监控配置的日志文件;当出现大量 error 级别的日志时,它会根据最近的遥测数据重建故障,并自动生成电影和简报。`incidentlens analyze --logs svc=incident.log` 则是在事后执行相同操作。详情:[docs/LIVE.md](docs/LIVE.md)。
在 [demo/model-id-typo/](demo/model-id-typo/) 中提供了一个可运行的端到端示例:`FAST_TIER_MODEL_ID` 中的单字符拼写错误(`vendor` 错写为 `modelhst`)导致每次 LLM 调用失败。有关该故障的任何信息都不是硬编码的——`incidentlens analyze --config demo/model-id-typo/incidentlens.config.json` 会读取原始日志,对其进行重建,将记录的故障归因于 `hary.models.llm_factory`,并使用静态调用图将 `get_llm_for_tier` 识别为候选故障点。将日志文件替换为您自己服务的真实日志,它的执行方式也是一样的。
## Studio:将故障变成一部电影
`incidentlens[studio]` 会将故障渲染为带有旁白的 MP4——一个连续的 3D 镜头,而不是幻灯片。透视摄像机在架构中穿梭,聚焦于旁白正在讲述的内容;服务是地平网格上的方块,随着状态的变化而升起、变色和跳动;请求流量以粒子的形式沿着依赖边流动,当故障穿过这些边时,粒子会变热并反向倒流;冲击波从状态变化处向外扩散;热点区域会产生光晕效果。所有这些都是用纯 Python 渲染并通过 ffmpeg 以 1080p30 的帧率输出的——不需要浏览器,也不需要 GPU——并且它是确定性的:视觉效果绝不会展示分析中未曾发现的任何内容。旁白是唯一具有生成性质的部分,由 LLM 编写故事,再由文本转语音的嗓音朗读出来;对于原因的陈述,仅采用证据指向的方向,而非将其作为绝对事实。
**深入剖析。** 当发生故障的服务的内部流水线已知时(参见下文的扫描器),镜头不会仅仅停留在服务边界。在故障发生的节拍上,摄像机会逐阶段跟踪请求——已记录或推断出的遍历路径显示为青色,处于休眠状态的上下文显示为暗色,而已记录的故障阶段则显示为红色。当存在 `incidentlens.codegraph.json` 时,它随后会打开按包分组的完整模块蓝图,建立每个模块和边的联系,滑入发生故障的包中,并叠加故障情况:日志归因的模块显示为红色,结构依赖风险显示为琥珀色,仅有静态上下文的部分保持暗色。最后一级会打开聚焦的函数蓝图,其中方法嵌套在类中,类嵌套在模块中。它最终停留在标记为琥珀色的候选函数上,并明确将其标记为静态推断,而非确认的运行时 stack frame。如果没有代码库的代码图,先前那种紧凑的依赖关系和调用者/被调用者视图将作为后备方案。
## 将代码视为网络
第三层深度。`incidentlens graph` 使用纯 `ast` 遍历代码库(不进行任何 import,不执行任何代码),并将每个服务的*代码*渲染为依赖网络——一个独立的 HTML 文件,无需 CDN:
```
incidentlens graph . --out code-graph.html
```
扫描现在将两个图合二为一,且在一次遍历中构建完成:
- **模块网络** — 每个内部模块都是一个节点;import 和*已解析的调用*(通过 import 别名,包含符号)是边。每个模块还带有其 **fan-in / fan-out**、其 **爆炸半径**(传递依赖于它的模块数量),以及它是否处于 import **环**中。
- **调用图** — `module.Class.method` / `module.function` 层。节点是函数、方法和类;它们之间的边是已解析的调用,本着 `pyan` 的精神尽力解析:`from x import f` 的来源、模块别名调用、`self.method()`、单次赋值的局部类型推断(`x = Thing(); x.foo()`)、构造函数调用,以及静态扫描通常会遗漏的**动态导入**(`importlib.import_module("a.b")`, `__import__`)。
在交互视图中点击任何节点,面板会直接回答凌晨两点(排查问题)时的疑问:**谁调用了它** 以及 **它调用了什么** —— `hary.guardrails.pii ← input_guardrail (scan)`,`hary.models.llm_factory.get_llm ← agent.AgentNode.__call__`。切换 **module ⁄ symbol** 以从文件深入到函数;节点按爆炸半径确定大小,处于环中的成员会被琥珀色虚线环绕,颜色则遵循其功能角色(endpoint · client · config · middleware · graph-node · logic)。支持搜索、缩放、拖拽;可在不同服务间切换。传入 `--analysis INC-….analysis.json`,故障就会落在地图上:遍历过的模块会被青色环标记,被关联错误日志指出的模块显示为红色,而静态选定的候选函数则被琥珀色虚线环绕。
### Mermaid,层级分明且带有颜色标记
`--mermaid` 还会输出一张 [Mermaid](https://mermaid.js.org) 图表(`.mmd` 源文件外加一个一键查看的 HTML)——可以将其粘贴到 PR、运行手册、GitHub 或 Mermaid Chart 连接器中:
```
incidentlens graph . --mermaid --level module # package-grouped overview
incidentlens graph . --mermaid --level symbol --focus hary.models.llm_factory
```
在 `--level symbol` 级别下,图表是真正的层级结构——**方法嵌套在 class 子图中,class 嵌套在 module 子图中**——每个角色都带有 `classDef` 颜色,并且会标记出耦合集群。由于真实服务的完整调用图过于密集,难以整体阅读,symbol 级别会使用 `--focus`(某个模块或 `module.Class.method`)限定范围,仅显示其直接的调用者和被调用者。当 `discover` 已经写入 `incidentlens.codegraph.json` 时,`analyze` 和 `watch` 除了视频之外,还会输出交互式 HTML 和以故障为核心的 `.mmd` 文件。
```
pip install -e ".[studio]" # plus ffmpeg
incidentlens studio gateway-auth-rejection --out incident.mp4
```
配置了 `OPENAI_API_KEY` 后,默认的 `auto` 语音会使用 OpenAI 平稳的
神经 TTS;如果没有密钥,则会回退到本地环境。当发生故障的服务存在
调用图时,旁白会指明候选的**函数**、谁调用了它、
它的结构爆炸半径,以及它所处的任何环——而不会声称
这种静态归因是一个运行时的 stack frame。添加
`--intro-video PATH` 可以在开头加上一段简短的 Sora/Veo/Runway 片头,而无需将
技术图表交给生成式模型处理。有关语音控制、长篇
合成、Genblaze 溯源以及 Backblaze B2 发布的信息,请参见
docs/STUDIO.md](docs/STUDIO.md)。
### Genblaze 媒体溯源
在 Python 3.11 或更高版本上安装附加的 Genblaze 集成:
```
pip install -e ".[studio,genblaze]" # plus ffmpeg
export OPENAI_API_KEY=...
export INCIDENTLENS_GENBLAZE_VOICE=coral
incidentlens studio gateway-auth-rejection \
--voice genblaze \
--publish-genblaze \
--out incident.mp4
```
这会通过单个 Genblaze
`OpenAITTSProvider` 管道处理每一段旁白节拍,将 prompt 可见性归类为私有,并且
将生成的 WAV 文件绑定到 SHA-256 声明。这种分类属于
溯源元数据,而非加密或脱敏:导出的标准
旁白清单仍然包含口述的旁白文本,因此请像保护任何
故障产物一样保护它。Genblaze 0.3.x 目前接受 `cor` 但不接受
较新的 `marin` 或 `cedar` 语音名称。对于 `marin` 和兼容的
自定义语音端点,直接使用 `--voice openai` 仍然是
默认途径。
`--publish-genblaze` 会保持 `incident.mp4` 在字节层面上完全不变,并在其旁边写入
`incident.genblaze.json`。如果使用 `--voice genblaze`,它还会写入一份
旁白清单。该旁白清单在标准上是可验证的,但其
节拍级别的 `file://` URL 指向的是渲染后会被删除的临时 WAV 文件;除非将这些音频文件单独
持久化保存,否则事后无法再次获取并进行验证。
要通过 Genblaze 的 B2 存储桶进行发布:
```
export B2_BUCKET=incidentlens-demo
export B2_REGION=us-west-004
export B2_KEY_ID=...
export B2_APP_KEY=...
incidentlens studio gateway-auth-rejection \
--voice genblaze \
--upload-genblaze-b2 \
--out incident.mp4
```
这会使用层级化的 key 上传原始的 MP4 和标准清单,
然后将其嵌入到本地的 MP4 中。因此,嵌入后的本地副本
与存储在 B2 中记录了 SHA 的原始对象在字节上是不同的。
`B2_APPLICATION_KEY` 和标准的 `B2_ENDPOINT_URL` 仍被接受为
旧版别名。旧的直接 `--upload-b2` 命令被单独保留,
并且仍然会返回一个有时限的预签名 URL。
## 引擎如何进行推理
默认引擎(`engines/deterministic.py`)是基于规则的,并且在源码中有完整的文档记录。没有机器学习,没有外部调用,对于相同的输入可重现输出。流水线如下:
1. **信号分类。** 每个遥测事件都会使用日志级别和指标行为,被标记为 baseline、change、failure、warning 或 recovery。
2. **指标异常检测。** 当指标序列上升至其自身序列内基线的 3 倍或以上(或下降至 1/3 或以下)时,即被视为异常;针对错误计数、≥95% 的饱和度、积压和极端延迟,会采用单点启发式算法。在出现异常后又恢复到接近基线水平的序列标志着系统已恢复。
3. **起源点识别。** 故障信号会在 90 秒的时间窗口内按服务进行合并;最早发生故障的服务即为起源点。
4. **变更关联度。** 根据起源故障对变更进行评分:基础分为 0.45,如果故障在 5 分钟内发生(15 分钟内为 +0.15)则加 +0.25,同服务加 +0.10,关键字匹配度加 +0.10(变更和故障中均出现 credential、capacity 或 config 等术语),上限为 0.95。如果分数低于 0.55,则该变更被报告为偶发事件,而非因果关系。
5. **传播映射。** 从起源点开始遍历依赖图。依赖于已降级服务的降级服务成为上游依赖步骤;被已降级服务所依赖的降级服务成为下游压力步骤。机制(超时、负载、积压)来自于对关联证据应用的关键字规则。
6. **假设组装。** 根本原因和传播状态为 `inferred`(传播得分上限为 0.88)。只有当面向用户的服务显示出直接的故障证据时,客户影响才被标记为 `confirmed`。遥测无法回答的问题会以置信度 0.0 输出为 `unknown`。
7. **来源验证。** 任何结论引用的每个证据 ID 都必须存在于证据集中,否则引擎将抛出异常。绝不出现悬空的引用。
阈值是模块顶部的常量,并附有解释每个选择的注释。不认同某个设定?非常欢迎提交 pull request。
## API
```
GET /api/v1/health
GET /api/v1/scenarios # list bundled scenarios
GET /api/v1/scenarios/{name} # architecture + raw events for one scenario
POST /api/v1/incidents/analyze # {"scenario": "checkout-secret-rotation"}
```
`analyze` 返回完整的 `IncidentAnalysis` JSON。未知场景返回 404。没有故障信号的遥测数据返回 422——在没有观察到故障的情况下,绝不凭空捏造故障。
## 架构
```
Telemetry sources (logs, metrics, deployments, architecture metadata)
│
▼
Connector interface ──► canonical event model
│
▼
Analysis engine (signals → anomalies → origin → change correlation
→ propagation → hypotheses → provenance check)
│
▼
IncidentAnalysis ──► replay UI · briefings · action plan · evidence record
```
```
src/incidentlens/
├── api.py FastAPI routes
├── cli.py `incidentlens serve`
├── domain/ models + errors
├── connectors/ telemetry integrations (synthetic included)
├── engines/ analysis engines (deterministic included)
├── services/ orchestration
├── data/scenarios/ bundled incidents (scenario.json, architecture.json, events.json)
└── static/ replay UI
```
## 编写连接器
连接器的作用是将遥测源适配到标准事件模型:
```
from incidentlens.connectors.base import TelemetryConnector
from incidentlens.domain.models import ArchitectureGraph, TelemetryEvent
class MyConnector(TelemetryConnector):
def fetch_events(self) -> list[TelemetryEvent]: ...
def fetch_architecture(self) -> ArchitectureGraph: ...
```
期待:OpenTelemetry、Datadog、CloudWatch、Grafana Loki、Elastic、Kubernetes 事件、GitHub Actions 部署。一个新的合成场景也是非常棒的首次贡献——只需在 `data/scenarios//` 中放入三个 JSON 文件,完全不需要 Python。
## 限制
当前版本的诚实清单:
- **仅限合成数据。** 所有七个场景均为手工构建。真实的连接器是路线图上的首要任务。
- **未处理时钟偏移。** 事件排序信任源时间戳。真实系统在进行跨源排序获得信任之前,需要具备对偏移的容忍度。
- **启发式阈值。** 3 倍的异常比率和 5/15 分钟的变更窗口是合理的默认值,而非通过学习得出的数值。
- **单一起源假设。** 并发的独立故障按最早发作时间排序;目前尚未对真正的多起源故障进行建模。
- **无身份验证,单一租户。** 目前这只是一个本地分析工具,而不是托管服务。
## 路线图
1. OpenTelemetry 连接器(输入真实遥测数据,进行相同的分析)
2. Studio 视频中状态间的动态过渡(目前是节拍之间的硬切换)
3. 事件排序中的时钟偏移容忍度
4. 回放导出(可共享的故障录像)
5. 根据分析文档生成复盘报告草稿
6. 基于策略的操作审批
## 开发
```
make install # editable install with dev deps
make lint # ruff + mypy (strict)
make test # pytest
make run # serve on 127.0.0.1:8000
```
CI 在每次 push 时都会运行同样的三项检查。
## 贡献 · 安全 · 许可证
参见 [CONTRIBUTING.md](CONTRIBUTING.md) 和 [SECURITY.md](SECURITY.md)。采用 Apache-2.0 许可证。
标签:API集成, 分布式系统, 可观测性, 响应大小分析, 故障排查, 根因分析, 自动化payload嵌入, 请求拦截, 运维, 逆向工具