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采集器, 可观测性, 故障排查, 服务拓扑, 测试用例, 用户代理, 自动化攻击, 请求拦截, 链路追踪