copperbox/tracemap
GitHub: copperbox/tracemap
TraceMap 是一款基于 OTLP 的可观测性服务,通过自动学习服务间调用拓扑并渲染为实时交互依赖图,帮助团队在事件响应中快速定位事故源头。
Stars: 0 | Forks: 0
# TraceMap
TraceMap 是一款用于事件响应的可观测性服务。它充当 OTLP
collector,通过学习您的服务已经发出的 traces 和
metrics 来了解您公司的整个服务拓扑结构,并将其呈现为实时的交互式
依赖图——这样您就可以在事故的*源头*发现它,而不是
逐个团队地从症状反推原因。
## 功能介绍
- **服务图** - 一个实时、可平移/缩放的已知服务图
(内部和外部)以及每个学习到的调用方 -> 依赖项的边。健康状况
通过节点边框和边上的热度(绿色/琥珀色/红色)来编码。边显示
数据流的方向:动画虚线流(速度追踪调用速率)
加上发光的数据包(纯 CSS offset-path,无逐帧 JS)从
每个依赖项传播到依赖它的服务,在
依赖方的边缘以箭头结束。动画反映了实际接收到的 traces:没有当前流量(或只有过时
metrics)的边会保持静止,而繁忙的边在每个周期中承载更多的数据包。边锚点具有方向感知能力:
每条边连接到节点上面向其对应节点的一侧,并且
共享同一侧的边会扇形展开,而不是汇聚到一个点上,因此
即使在循环图中或手动
拖动之后,依赖方向也依然清晰可读。节点可以
被拖动到自定义位置(边会跟随;位置会在本地持久化,并且
重置布局按钮会恢复默认布局)。无需选择日期:该图始终
显示一切当前(或最后已知)的状态,即使是最近没有
流量的服务也是如此。
- **团队框架和 meganode** - 服务被分配给所属团队,
当存在 `team.name` OTEL 资源属性时(团队会自动
即时创建),或者通过 UI/API 手动分配。每个
未合并团队的服务都被布局在一个带标签的“框架”内
(就像基础设施图上的一个框),因此节点的所属权始终
可见。框架的标题栏带有团队名称、成员计数,以及一个
将团队折叠为单个 meganode 的合并按钮;拖动
标题栏可一次移动整个团队。合并每个团队(“合并所有团队”
快捷方式)会将数百个节点变成清晰的
“团队依赖团队”视图,并且任何单个 meganode 都可以取消合并回
其带框架的服务,以便进行选择性的深入分析。外部依赖项
将保持独立,直到您手动将它们分配给某个团队。合并更改
就地发生:切换时团队保持其在图上的位置(包括
您已拖动到其他位置的团队),其他框架仅移动
刚好足以避免重叠的幅度。更改带有动画效果——合并的服务明显收敛
到它们的 meganode 中,取消合并的服务从中飞出——因此
始终能清楚看到哪些节点刚刚合并或拆分。聚合团队通常会使图变成循环的(大多数
团队最终变得相互依赖);布局通过
贪婪反馈弧排序打破这些循环,因此主流仍然自上而下运行,
只有最少数量的反向边指向上方。
- **图表类型(Map / Communities)** - 右上角的切换开关可以在
服务图的两种布局之间切换。“Map”是上述的默认分层依赖流
视图。“Communities”是在 canvas 上渲染的力导向图,
因此它可以扩展到数百个节点:服务变成
Obsidian 风格的点,大小由流量决定,颜色由社区决定,边作为
细线,标签在悬停/选中或放大时出现。
社区颜色仅限于冷色调(从青色到品红色),因此它们绝对不会
与节点状态环使用的红/琥珀/绿颜色混淆。
社区是通过在调用图上进行标签传播自动检测到的
(仅基于结构,因此它们在 metric 轮询期间保持稳定,并且仅在拓扑结构发生变化时才改变),揭示了独立于团队所有权的紧密耦合服务集群。模拟在第一次
绘制之前预设稳定,并在您拖动节点(这会使其重新加热)之前保持冷却状态,因此空闲的
图不消耗任何 CPU。两个视图共享相同的选择/聚焦/搜索/团队
过滤以及相同的检查抽屉。团队过滤器是一个可搜索的
下拉菜单(输入以缩小团队列表范围,选择一个团队或为
无所有者的服务选择“Unassigned”),在地图和服务列表中重复使用。
- **服务列表** - 包含每个服务的可排序表格(健康状况、流量、p95、
错误率、30 天 SLO 以及 24 小时延迟迷你图),按健康状况最差的
优先排序。共享的可搜索团队过滤器将表格限定为一个团队——或者对于不属于任何团队的服务限定为“Unassigned”——全局搜索
框通过名称缩小范围。
- **检查抽屉** - 点击任何节点、meganode 或边,检查 SLO 达成率
+ error budget、KPI、24 小时迷你图(悬停一个图会使所有的十字准线
移动,因此很容易比较同一时刻)、
调用方/依赖项,以及边的:学习到的关系(首次观察到、支持的 spans、置信度、自动还是手动来源)和
观察到的操作组合。选择节点或边还会列出其发生错误最多的操作,并在每个操作下列出在 traces 中实际看到的不同错误
(异常类型、HTTP 状态码、queue/db 错误代码)及其计数。
- **服务页面** - 每个服务的深度分析,包含 KPI 卡片、
延迟/吞吐量/错误率图表(悬停十字准线工具提示在
各图表之间保持同步)、主要
操作、关联的上游/下游服务以及最近的 traces。
一个主要出错操作面板列出了每个失败操作及其
观察到的不同错误;点击其中一个会将最近的 traces 列表过滤为
该操作的失败 traces(再次单击或单击过滤器标签即可清除)。
Kibana 风格的日期/时间选择器(快速范围或绝对开始/结束时间)限定
页面上的所有内容。每个部分(页眉/KPI/图表、主要错误、最近
traces)在其独立的微光骨架屏后独立加载,较重的
chart bundle 被 code-split,因此页面外壳会立即绘制,而不会
阻塞在一个组合请求上。
- **Trace waterfall** - 点击 trace 打开完整的分布式 span 树
,查看时间条和原始 OTEL span 属性。
- **手动管理** - 重命名服务、设置描述、所属团队/组、
类型和 SLO 目标;手动关联推断无法
看到的依赖项;并将重复服务(以不同名称报告的同一服务)合并为一个规范服务——历史遥测数据会被重新指向,并且
旧名称将成为所有未来流量的别名。重复项选择器与在其他地方使用的可搜索下拉菜单相同,范围限定为没有团队的服务
(已分配的服务是明确拥有的,不是零散的重复项)。合并是
可逆的:每次合并都会被记录,并且重新指向的遥测会被标记,因此
编辑模态框会列出合并到服务中的所有内容,而“取消合并”按钮
会将重复项重新拆分出来——恢复其服务、遥测和边。
## 架构
```
your services --OTLP/HTTP--> server (Fastify, :4318) --> TimescaleDB
| spans + edge_events hypertables
| continuous aggregates (1m)
| 30-day retention policies
browser <----- web (React) <-- query API (:4000)
```
- `server/` - Node.js + TypeScript。
- **OTLP collector** (`:4318`):接收 `POST /v1/traces` 和
`POST /v1/metrics`,支持 `application/x-protobuf`(内部内置
opentelemetry-proto 定义)和 `application/json`,符合
[OTLP 规范](https://opentelemetry.io/docs/specs/otlp/)。
- **拓扑推断**:跨服务的边是通过对 CLIENT
spans 和由其引起的 SERVER spans 进行连接来学习的(无论批量到达顺序如何均可工作);从不发出遥测的数据库、queues 和外部 API 均通过语义约定属性推断得出
(`db.system`、
`messaging.system`、`peer.service`、`server.address`、`url.full` 等)。
边指标始终是*调用方*对依赖项的度量。
- **查询 API** (`:4000`):拓扑、服务列表/详情、时间序列、
traces、团队以及管理端点(重命名/合并/手动连线)。
- `web/` - React + Vite + zustand。自定义 SVG 地图(分层 DAG 布局 - 叶
依赖项在顶部,gateway 在底部)、图表以及来自设计交付的完整 UI
(深色/浅色主题,绿色强调色)。
- **TimescaleDB** (Postgres + timescaledb_toolkit):原始的 `spans` 和
`edge_events` hypertables 拥有每分钟的连续聚合
(用于 p50/p95/p99 的 `percentile_agg` 草图)支持所有图表,并在所有遥测上自动执行
**30天保留** 策略。
## 快速开始
需要 Docker(本地开发需要 Node 22+)。
```
# 所有内容都在 containers 中(db + collector/API + web 位于 :5173)
docker compose up -d --build
# 或者:database 在 Docker 中,apps 在本地运行(最适合开发)
docker compose up -d db
npm install
npm run migrate # apply schema to the db
npm run dev # server (:4000 api, :4318 otlp) + web (:5173)
# 可选:demo 流量 - 回放 40 个服务的 e-commerce topology
# (带有实时 incident narrative)作为真实的 OTLP/JSON exports
npm run simulate
```
打开 http://localhost:5173。将真实服务指向
`http://:4318/v1/traces`(标准 OTLP/HTTP exporter 设置)。
## 接入您的服务
从零到受管实时地图的最佳路径:
1. **运行 TraceMap** (`docker compose up -d --build`) 并打开 UI。
2. **将 OTLP exporter 指向 collector。**任何 OTEL SDK 或 collector 均可:
将 OTLP/HTTP endpoint 设置为 `http://:4318`。`service.name` 是
唯一必需的资源属性——每个唯一的名称在其
第一个 trace 时就会成为一个地图节点,并且调用方 -> 依赖项的边会自动学习得出。
3. **通过 `team.name` 资源属性声明所有权。**最简单的
方法是使用标准环境变量——无需更改代码:
OTEL_SERVICE_NAME=checkout-svc
OTEL_RESOURCE_ATTRIBUTES=team.name=Checkout
当来自服务的第一个 trace 到达时,如果
该团队不存在,TraceMap 会创建它并将服务分配给它。该属性仅填充*缺失的*分配——它永远不会覆盖通过
UI 或 API 设置的团队,因此手动管理保持权威性。在大规模应用中,应从 OTel Collector processor(拉取 pod label 的
`k8sattributes`,或 `resource` processor)集中
注入该属性,而不是在每个服务中注入。
4. **管理推断出的依赖项。**数据库、queues 和外部 SaaS
API 从不发出自己的遥测,因此它们显示为没有团队的推断节点。
在 UI 中(或使用 `PATCH /api/services/:id` 传入
`{"teamName": "...", "type": "..."}`)分配它们的团队和类型。
5. **清理重复项。**如果相同的服务以不同的名称报告,
请将它们合并——历史记录会被重新指向,旧名称会变为别名。
内置的模拟器(`npm run simulate`)演示了这一确切流程:其
被监控的服务在每个 trace 中都带有 `team.name`,因此团队会被创建,
并且所有权完全通过遥测进行分配。它的数据库、queues 和
SaaS peers 不会发出自己的遥测,因此——就像真实的推断
依赖项一样——它们会以未分配的形式出现,供您在第 4 步中进行管理。
## 配置
| 环境变量 (server) | 默认值 | 用途 |
| ---------------------- | ------------------------------------------------------- | -------------------------------- |
| `DATABASE_URL` | `postgres://tracemap:tracemap@localhost:5433/tracemap` | TimescaleDB 连接 |
| `PORT` | `4000` | 查询/管理 API |
| `OTLP_PORT` | `4318` | OTLP/HTTP collector |
| `EDGE_RESOLVE_TTL_MS` | `90000` | 跨批量 span 连接窗口 |
| `LIVE_WINDOW_MINUTES` | `5` | 用于 map metrics 的“当前”窗口 |
模拟器标志:`npm run simulate -- --otlp http://127.0.0.1:4318 --api http://127.0.0.1:4000 --tps 6`。
在终端中运行模拟器时,实时调整 trace 速率:`+` 使其翻倍,
`-` 使其减半(限制在 0.25-96 traces/s),`0`/空格/`p` 暂停并
恢复,`q` 退出。
为了进行压力测试和演示,可以将拓扑结构扩展到超出默认受管的
40 个服务演示(该演示始终作为可识别的核心保留;合成的
团队和服务被叠加在其上):
| 标志 | 环境变量 | 默认值 | 用途 |
| -------------- | --------------- | ------- | ----------------------------------------------------------------------- |
| `--services` | `SIM_SERVICES` | `0` | 目标节点总数;通过在演示基础上增加合成团队来达到该目标(0 = 仅演示)。 |
| `--teams` | `SIM_TEAMS` | `0` | 将生成的服务分布到的合成团队数量(0 = 从节点数量推导)。 |
| `--unassigned` | `SIM_UNASSIGNED`| `0` | 要铸造的无团队推断 peers 的数量(从不植入种子——它们保持未分配状态)。 |
| `--dup-ratio` | `SIM_DUP_RATIO` | `0.4` 铸造为重复对的无主 peers 的比例:一个后端使用两个名称,用于测试合并。 |
合成团队拥有数量不等的服务,因此地图显示出真实的
大小差异。未分配的重复对(例如 `billing-2.example.com`
和 `api.billing-2.io`,或 `media-cdn-1-db` 和 `pg-media-cdn-1`)是代表同一个后端的两个
独立的推断节点——在 UI 中或
使用 `POST /api/services/:id/merge` 合并它们以练习关联。示例:
`npm run simulate -- --services 300 --unassigned 40 --dup-ratio 0.5 --tps 24`。
## API 概览
| Endpoint | 用途 |
| --- | --- |
| `POST /v1/traces`, `POST /v1/metrics` (`:4318`) | OTLP/HTTP 接收 (protobuf + JSON) |
| `GET /api/topology` | 完整实时地图:服务、边、团队、当前 metrics |
| `GET /api/services` | 带有迷你图 + SLO 的服务列表 |
| `GET /api/services/:id?from=&to=` | 详情:KPI、时间序列、操作、邻居 |
| `GET /api/services/:id/traces?from=&to=&op=` | 涉及某服务的最近 traces(`op` 过滤该操作的失败情况) |
| `GET /api/services/:id/errors?from=&to=` | 出错最多的操作 + 出现的错误 |
| `GET /api/edges/:source/:target/errors` | 出错最多的操作 + 某条边上出现的错误 |
| `GET /api/traces/:traceId` | 用于 waterfall 的完整 trace |
| `PATCH /api/services/:id` | 重命名 / 描述 / 团队 / 类型 / SLO 目标 |
| `POST /api/services/:id/merge` | 将重复服务合并到此服务(为其名称创建别名) |
| `POST /api/services/:id/unmerge` | 撤销合并,将重复服务拆分回其独立的服务 |
| `POST/DELETE /api/services/:id/dependencies` | 手动依赖关联 |
| `GET/POST /api/teams` | 团队(组)管理 |
| `GET /api/health` | 接收活跃度 + 计数器 |
## 测试
```
npm test # server (OTLP decode, peer inference, edge resolver) + web (layout, grouping, formatters)
```
## 项目布局
模块保持专注的关注点范围:路由、模拟器阶段和 UI
部分被拆分为小文件,而不是单体结构。在 web 端,
组件样式位于并置的 CSS Modules 中(`X.module.css` 紧挨着
`X.tsx`);只有真正的动态值(计算的 transforms、按数据计算的
颜色)保持内联,共享的设计 token 是
`web/src/theme/global.css` 中的 CSS 变量。
```
server/src/otlp/ OTLP decode (vendored protos), peer inference, edge resolver, ingest
server/src/api/ routes split per resource (service list/detail/edit/merge,
topology, traces, teams, series) + shared range parsing
server/src/db/ migrations (TimescaleDB schema), pool
server/src/sim/ demo traffic generator, split by stage (args, topology +
procedural augmentation, trace gen, OTLP payload encoding,
http, metrics sampling)
web/src/lib/ DAG layout, team grouping, community detection, time ranges, formatters, timeSince
web/src/theme/ global CSS tokens/keyframes + font shorthand helpers
web/src/features/ map (MapView switches LayeredMap / force/ communities graph,
view/ render layers, MapDrawer + drawer/ panels),
services list, service page (+ sections/), trace waterfall
web/src/components/ top bar, charts, sparklines, SLO ring, icons
```
## 注意事项与局限性
- 合并服务会重写历史记录行并刷新聚合;
在超大规模数据集上,这是一项繁重的管理操作。
- 删除*学习到的*边会移除它,直到新的遥测重新学习到它;
删除*手动*连线则是永久的。
- 未实现 OTLP/gRPC (`:4317`);请使用 OTLP/HTTP (`:4318`),每个
OTEL SDK 和 collector 都支持它。
标签:API集成, MITM代理, OTLP采集器, 可观测性, 故障排查, 服务拓扑, 测试用例, 用户代理, 自动化攻击, 请求拦截, 链路追踪