This is an automated email from the ASF dual-hosted git repository. wu-sheng pushed a commit to branch blog/horizon-ui-cn-polish in repository https://gitbox.apache.org/repos/asf/skywalking-website.git
commit 6fcd725c069a618b1f0e508fcd6a0726f6838ffb Author: Wu Sheng <[email protected]> AuthorDate: Mon Jun 29 10:05:34 2026 +0800 docs(blog): refine Horizon UI Chinese translations --- .../index.md | 26 +++++++++--------- .../index.md | 20 +++++++------- .../index.md | 28 +++++++++---------- .../index.md | 30 ++++++++++---------- .../index.md | 24 ++++++++-------- .../2026-06-22-horizon-ui-trace-explorer/index.md | 30 ++++++++++---------- .../index.md | 32 +++++++++++----------- .../zh/2026-06-23-horizon-ui-log-explorer/index.md | 18 ++++++------ 8 files changed, 104 insertions(+), 104 deletions(-) diff --git a/content/zh/2026-06-21-horizon-ui-dashboards-and-mqe/index.md b/content/zh/2026-06-21-horizon-ui-dashboards-and-mqe/index.md index 188e9addc75..6bfc381463b 100644 --- a/content/zh/2026-06-21-horizon-ui-dashboards-and-mqe/index.md +++ b/content/zh/2026-06-21-horizon-ui-dashboards-and-mqe/index.md @@ -2,7 +2,7 @@ title: "认识 Horizon UI · 2/17:动态仪表盘、MQE 与指标格式化" date: 2026-06-21 author: 吴晟 -description: "Horizon UI 系列第二篇:仪表盘如何由 MQE 表达式驱动,如何按当前服务、实例或 endpoint 自动取舍组件并跳过无关查询,以及如何把编码、耗时和大体量指标值格式化成适合排查时阅读的值。" +description: "Horizon UI 系列第二篇:仪表盘如何由 MQE 表达式驱动,如何按当前服务、实例或 endpoint 自动取舍组件并跳过无关查询,以及如何把编码值、耗时和大数值格式化成适合排查时阅读的样子。" tags: - Metrics - Cloud Native @@ -16,7 +16,7 @@ Horizon 里的每个仪表盘,本质上都使用同一套机制:一个组件 ## 每个组件都是一条 MQE 表达式 -Layer 仪表盘是一个紧凑的 **12 列网格**:行高 120px,空隙会自动回填,避免墙一样的组件块出现洞;宽度低于约 1100px 时会折叠成单列。每个格子是五类组件之一,而组件类型取决于 MQE 表达式返回数据的 *形状*: +Layer 仪表盘是一个紧凑的 **12 列网格**:行高 120px,空隙会自动回填,避免组件墙上出现空洞;宽度低于约 1100px 时会折叠成单列。每个格子是五类组件之一,而组件类型取决于 MQE 表达式返回数据的 *形状*: - **`card`**:表达式收敛为单个标量,比如 `latest(...)`、`avg(...)`、`service_sla/100`。显示一个主指标值。 - **`line`**:时间序列;每个表达式一条线,混合单位时可以使用双 Y 轴,比如左侧吞吐、右侧时延。 @@ -24,24 +24,24 @@ Layer 仪表盘是一个紧凑的 **12 列网格**:行高 120px,空隙会自 - **`record`**:记录型输出,比如慢数据库语句或慢缓存命令:文本行加数值。 - **`table`**:带标签的 `latest(...)` 指标,每组 label 一个表格行,比如每个服务的 pod phase、node condition、deployment replicas。 -你不需要手工选择图表类型;写好 MQE,合适的组件会自己渲染。而且 service、instance、endpoint 这些层级使用同一套网格系统。Layer 模板里的 `dashboards.<scope>` map 会为不同页面配置不同组件集合,所以向下钻取时,仪表盘会切到对应作用域。(这些都运行在[第一篇](/zh/2026-06-21-skywalking-horizon-ui-introduction/)介绍的 BFF 层;浏览器不直接访问 OAP。) +你不需要手工选择图表类型;写好 MQE,合适的组件会自己渲染。而且 service、instance、endpoint 这些层级使用同一套网格系统。Layer 模板里的 `dashboards.<scope>` map 会为不同页面配置不同组件集合,所以向下钻取时,仪表盘会切到对应作用域。(这些都运行在[第一篇](/zh/2026-06-21-skywalking-horizon-ui-introduction/)介绍的 BFF 上;浏览器不直接访问 OAP。) -## 根据当前对象决定显示哪些组件 +## 按当前服务或实例取舍组件 这个设计会明显改变仪表盘的使用感受。组件可以带一个 **`visibleWhen`** 条件。如果条件不成立,组件不渲染;更关键的是,**它的查询也不会执行**。 条件有两类: - **MQE metric**:只有表达式 *有值* 时展示组件(`op: exists`),或者只有值超过阈值时展示(`gt` / `lt`)。组件可以拿自己的指标做自我开关:JVM 组件带 `"visibleWhen": { "kind": "mqe", "expression": "instance_jvm_cpu", "op": "exists" }`,所以它们会出现在 Java 实例上,在 Go 实例上消失。 -- **Entity attribute**:在 Instance 作用域上,根据选中实例的属性开关,比如 `language eq JAVA`,或者某个属性是否存在。 +- **Entity attribute**:在 Instance 作用域上,根据选中实例的属性开关,比如 `language eq JAVA`,或者判断某个属性是否存在。 -因为条件在 **服务端** 计算,非 JVM 实例不只是把 JVM 格子藏起来;BFF 根本不会把这些查询发给 OAP。用同一个 Instance 仪表盘打开一个 JVM 服务和一个非 JVM 服务,你看到的是同一个模板根据当前对象自动取舍,而不是两套手工维护的页面。 +因为条件在 **服务端** 计算,非 JVM 实例不只是把 JVM 格子藏起来;BFF 根本不会把这些查询发给 OAP。用同一个 Instance 仪表盘打开 JVM 实例和 Go 实例时,你看到的是同一个模板按当前实例自动取舍,而不是两套手工维护的页面。  图 1:JVM 实例上会渲染 JVM 组件,因为它们的 `visibleWhen` 条件成立。</br>  -图 2:同一个仪表盘打开在 Go 实例上,JVM 组件不存在,查询也没有执行。同一个模板,会根据当前对象调整内容。</br> +图 2:同一个仪表盘打开在 Go 实例上,JVM 组件不存在,查询也没有执行。同一个模板,会按当前实例调整内容。</br> ## 指标值的显示格式 @@ -52,35 +52,35 @@ Layer 仪表盘是一个紧凑的 **12 列网格**:行高 120px,空隙会自 - **SI suffixes**:图表坐标轴和 tooltip 上的大数会显示成 **`45.1k`**、**`1.34M`**、**`2.5G`**,而不是 `4.51e4`。坐标轴刻度和 hover 值使用同一套写法。  -图 3:高密度字节数和计数序列使用紧凑 SI 后缀,坐标轴和 tooltip 保持一致。</br> +图 3:字节数和计数这类大数序列使用紧凑 SI 后缀,坐标轴和 tooltip 保持一致。</br>  图 4:`enum` 和 `duration` 格式在 BanyanDB 生命周期卡片上的效果:`OK` 代替 `1`,"5m 20s ago" 代替秒数。</br> ## 按同一条时间线阅读整页图表 -页面上所有 `line` 图共享 **同一个 hover 游标**。指向吞吐图上的第 32 分钟,时延图、错误率图和每个 sparkline 格子上的第 32 分钟也会一起亮起。这个契约在图表 wrapper 层强制执行,任何组件都不能退出,所以页面读起来是一组围绕同一时刻协同的视图,而不是十几个互不相关的图。多序列 tooltip 是固定对齐的表格,展示每条序列的 **title**(不会显示原始 MQE),所有值在同一列右对齐。 +页面上所有 `line` 图共享 **同一个 hover 游标**。指向吞吐图上的第 32 分钟,时延图、错误率图和每个 sparkline 格子上的第 32 分钟也会一起亮起。这个约定在图表封装层强制执行,任何组件都不能退出,所以页面读起来是一组围绕同一时刻协同的视图,而不是十几个互不相关的图。多序列 tooltip 是固定对齐的表格,展示每条序列的 **title**(不会显示原始 MQE),所有值在同一列右对齐。  图 5:一个游标跨过页面上所有折线图,所以你可以在所有图上同时读同一瞬间。</br> ## 从慢记录直接打开 Trace -`record` 组件,比如 Slow Statements、Slow Commands、Slow Database Statements,是采样记录列表。每一行如果带 trace id,行首会出现一个 **jump-to-trace** 图标,点击即可打开对应 Trace 的瀑布图。它按 **trace id** 解析,而不是按 Layer 解析,这点很重要:Virtual Database / Cache / MQ 服务上的 Slow Statements 属于另一个 Layer 上的 *caller*,虚拟目标 Layer 自己没有 traces 标签页,但跳转仍然能打开正确 Trace。语句文本本身也支持 **点击复制**。 +`record` 组件,比如 Slow Statements、Slow Commands、Slow Database Statements,是采样记录列表。每一行如果带 trace id,行首会出现一个 **jump-to-trace** 图标,点击即可打开对应 Trace 的瀑布图。它按 **trace id** 解析,而不是按 Layer 解析,这点很重要:Virtual Database / Cache / MQ 服务上的 Slow Statements 属于另一个 Layer 上的 *caller*,虚拟目标 Layer 自己没有 traces 标签页,但跳转仍然能打开正确的 Trace。语句文本本身也支持 **点击复制**。  图 6:从慢语句跳到执行它的 Trace。按 trace id 解析,所以即使虚拟 Layer 自己没有 traces 标签页也能工作。</br> ## 固定多个对象做对比 -有时只看一个对象不够。Horizon 允许你 **锁定多个服务、实例或 endpoint,甚至跨服务锁定,并在当前页面内直接比较**。可以从选择器或 instance/endpoint 列表固定对象;当前正在查看的对象始终属于对比组,会标记为 `CURRENT`,并继续驱动顶部信息区。每个固定对象都有自己的颜色。之后每个组件都会就地对比:line 组件为每个对象叠加一条序列,card 为每个对象显示一行,`top` 和 `record` 组件增加按对象切换的标签页,table 增加 Entity 列。持久的对比栏会保持这组对象,不受底层列表分页或当前查看对象变化影响;每个对象单独发起请求,所以一个对象响应慢,不会把其他对象拖成空白。 +有时只看一个对象不够。Horizon 允许你 **锁定多个服务、实例或 endpoint,甚至跨服务锁定,并在当前页面内直接比较**。可以从选择器或 instance/endpoint 列表固定对象;当前正在查看的对象始终属于对比组,会标记为 `CURRENT`,并继续驱动顶部信息区。每个固定对象都有自己的颜色。之后每个组件都会就地对比:line 组件为每个对象叠加一条序列,card 为每个对象显示一行,`top` 和 `record` 组件增加按对象切换的标签页,table 增加 Entity 列。对比栏会一直保留这组对象,不受底层列表分页或当前查看对象变化影响;每个对象单独发起请求,所以一个对象响应慢,不会把其他对象拖成空白。  图 7:锁定对象,甚至跨服务锁定,所有折线组件都会按颜色叠加;对比栏保持这组对象,CURRENT 对象仍然驱动顶部信息区。</br> ## 时间范围作用于整个仪表盘 -顶部时间范围会驱动页面上的所有内容:顶部 KPI 区、组件主体,以及 BanyanDB 分层 hot/warm/cold 存储里 **Cold** 标记相关的整条路径。过去落地页和拓扑路由固定在最近 60 分钟,所以选择 "12 days ago" 后仍然会悄悄展示近期数字;现在时间选择器在所有地方都生效。上游控制变化时,每个依赖它的格子会明确重置并显示 "Reading data..." 提示,而不是在加载动画下面留着旧值。 +顶部时间范围会驱动页面上的所有内容:顶部 KPI 区、组件主体,以及 BanyanDB 分层 hot/warm/cold 存储里和 **Cold** 标记相关的整段排查路径。过去落地页和拓扑路由固定在最近 60 分钟,所以选择 "12 days ago" 后仍然会悄悄展示近期数字;现在时间选择器在所有地方都生效。上游控制变化时,每个依赖它的格子会明确重置并显示 "Reading data..." 提示,而不是在加载动画下面留着旧值。 ## 后续阅读 @@ -88,4 +88,4 @@ Layer 仪表盘是一个紧凑的 **12 列网格**:行高 120px,空隙会自 上面讲的是 *查看* 体验。每个组件的 MQE、`visibleWhen` 条件、格式,以及各作用域的网格,都可以从 **Layer dashboards** 管理界面编辑。但这个创作流程(draft → preview → publish,以及可内联/展开的 MQE 编辑器)会在后续文章里单独展开。字段级参考可以看文档里的 [dashboard widgets](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/components/dashboard-widgets/) 和 [charts](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/components/charts/)。 -下一篇:**topology and service dependency**,把 Horizon 在这里用图表展示的同一批数据,画成一张可以走进去看的地图。 +下一篇讲拓扑与服务依赖:同一批观测数据,如何从图表变成服务关系图,并继续下钻到实例和 endpoint。 diff --git a/content/zh/2026-06-21-horizon-ui-deployment-and-banyandb/index.md b/content/zh/2026-06-21-horizon-ui-deployment-and-banyandb/index.md index 8607bb1d5c2..52dd8c81a9c 100644 --- a/content/zh/2026-06-21-horizon-ui-deployment-and-banyandb/index.md +++ b/content/zh/2026-06-21-horizon-ui-deployment-and-banyandb/index.md @@ -2,7 +2,7 @@ title: "认识 Horizon UI · 4/17:Deployment 标签页与 BanyanDB 自观测" date: 2026-06-21 author: 吴晟 -description: "Horizon UI 系列第四篇:Deployment 标签页如何把拓扑视角收进单个集群服务内部,并以 BanyanDB 为例说明 SkyWalking 如何观测自己的存储引擎。" +description: "Horizon UI 系列第四篇:Deployment 标签页如何把拓扑视角转向单个集群服务内部,并以 BanyanDB 为例说明 SkyWalking 如何观测自己的存储引擎。" tags: - Cloud Native - Storage @@ -10,7 +10,7 @@ tags: *译自英文原文:[Meet Horizon UI · 4/17: The Deployment Tab & BanyanDB Self-Observability](/blog/2026-06-21-horizon-ui-deployment-and-banyandb/)。* -这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第四篇。[第三篇](/zh/2026-06-21-horizon-ui-topology-and-dependency/)画的是服务 *之间* 的地图。这一篇把视角收回来,画一个集群服务 *内部* 的实例关系,并用它解决 SkyWalking 过去一直没有很好展示的问题:把 **自己的存储引擎** BanyanDB 按真实集群形态呈现出来。 +这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第四篇。[第三篇](/zh/2026-06-21-horizon-ui-topology-and-dependency/)画的是服务 *之间* 的地图。这一篇把视角收回来,画一个集群服务 *内部* 的实例关系,并用它解决 SkyWalking 过去一直展示得不够清楚的对象:**自己的存储引擎** BanyanDB。 ## Deployment 标签页:查看服务内部实例关系 @@ -20,19 +20,19 @@ tags: - **实例渲染成六边形,并组合成 pod。** 一个 pod 的 **main** container 是完整六边形,**sibling** containers 会作为更小的六边形贴在它边上。所以主进程和 sidecar 会作为一个单元呈现,跨 pod 的 sidecar 链接也会连到它真正所属的小六边形上。 - **Pod 按你选择的规则聚到带标签的分组框里。** 规则可以是单个实例属性(role)、多个属性组合(比如 `node_role` + `node_type`),也可以是名称正则。这样一组混合角色节点会按角色分框展示,而不是堆成一团。 -- **布局是分层的。** 每个 cluster box 会按调用深度摆放 pod:source 在左,被调用对象在右,所以 upstream→downstream 链条从左到右就能看清;拖动任意 pod 后,它所属的 box 会重新流式布局,保持内容都被包住。 +- **布局是分层的。** 每个 cluster box 会按调用深度摆放 pod:source 在左,被调用对象在右,所以 upstream→downstream 链条从左到右就能看清;拖动任意 pod 后,它所属的 box 会重新流式布局,保证内容仍在分组框内。 -边按 **(source-role → target-role)** pair 区分,所以每类链接展示自己的指标,而不是共享一组扁平指标。主指标会直接印在边上,完整指标则进入 **Flows** 子标签页,每种 role-pair 对应一张对齐表。它默认关闭,和服务地图一样,完全从 **Layer dashboards admin → Deployment** 作用域配置。 +边按 **(source-role → target-role)** pair 区分,所以每类链接展示自己的指标,而不是所有边共用一组指标。主指标会直接印在边上,完整指标则进入 **Flows** 子标签页,每种 role-pair 对应一张对齐表。它默认关闭,和服务地图一样,完全从 **Layer dashboards admin → Deployment** 作用域配置。 ## 把 BanyanDB 纳入 SkyWalking 自观测 -这套机制不是为了多画一种图,它首先服务于一个具体场景。SkyWalking 的原生数据库 **BanyanDB** 是一个集群化、按角色和 tier 组织的系统,而过去 SkyWalking 很难把它作为一个整体观测清楚。新的 **BanyanDB** Layer 位于 **Self-Observability** 下,并配合 OAP 后端 SWIP-15,把通过 BanyanDB FODC proxy 抓到的指标建模成整个部署: +这套机制不是为了多画一种图,它首先服务于一个具体场景。SkyWalking 的原生数据库 **BanyanDB** 是一个集群化、按角色和 tier 组织的系统,而过去 SkyWalking 很难把它作为一个整体观测清楚。新的 **BanyanDB** Layer 位于 **Self-Observability** 下,并配合 OAP 后端 SWIP-15,把通过 BanyanDB FODC proxy 抓到的指标建模为整个部署: - 整个 **cluster** 是一个 **Cluster**(service); - 每个 container 是一个 **Container**(instance),并携带 `container_name` **role** 和 `node_type` **tier** 属性; - 每个存储 **Group** 是一个 **endpoint**。 -所以其他 Layer 中通用的 Service / Instance / Endpoint 导航结构,在 BanyanDB 这里就变成 **Cluster / Container / Group**。叠在上面的 Deployment 标签页,会直接画出这个数据库集群自身。 +所以其他 Layer 中通用的 Service / Instance / Endpoint 导航结构,在 BanyanDB 这里就变成 **Cluster / Container / Group**。基于这套模型,Deployment 标签页可以直接画出数据库集群自身。  图 1:SkyWalking 观测自己的数据库:BanyanDB 集群按角色和 tier 绘制,并展示 pod 之间的 liaison→data 与 lifecycle→data 边。</br> @@ -50,7 +50,7 @@ tags: <figcaption style="flex:1 1 240px;color:#5f6b7a;font-size:.92em;line-height:1.6;">图 2:Cluster 作用域,一眼看完整个数据库,并按角色列出所有容器。</figcaption> </figure> -打开 *同一个* Container 仪表盘到两个不同 role 上,最容易看出 role-gating 的效果: +把 *同一个* Container 仪表盘分别打开到两个不同 role 上,最容易看出 role-gating 的效果: <figure style="display:flex;flex-wrap:wrap;gap:1.1rem;align-items:center;margin:1.5rem 0;"> <img src="/screenshots/horizon-0.7.0/p04-deployment-03-container-dashboard.webp" alt="data/liaison 节点的 Container 仪表盘,在共享 CPU / memory / Go-runtime 资源面板上方显示角色专属的 ingestion / storage 面板。" style="max-height:320px;max-width:100%;width:auto;border-radius:4px;"> @@ -66,7 +66,7 @@ tags: <figure style="display:flex;flex-wrap:wrap;gap:1.1rem;align-items:center;margin:1.5rem 0;"> <img src="/screenshots/horizon-0.7.0/p04-deployment-04-group-dashboard.webp" alt="Group 仪表盘:某个 storage catalog 的按 data model 配置的面板。" style="max-height:320px;max-width:100%;width:auto;border-radius:4px;"> -<figcaption style="flex:1 1 240px;color:#5f6b7a;font-size:.92em;line-height:1.6;">图 5:Group 作用域,一次查看一个 storage catalog,它的面板按 group 的 data model 开关。</figcaption> +<figcaption style="flex:1 1 240px;color:#5f6b7a;font-size:.92em;line-height:1.6;">图 5:Group 作用域,一次查看一个 storage catalog,面板按 group 的 data model 开关。</figcaption> </figure> ### Role pair 边与 Flows 表 @@ -74,7 +74,7 @@ tags: Deployment 标签页中,容器之间的调用边会从 SWIP-15 instance-relation families 中拿到 **role-pair-specific** 指标:**liaison → data** 边展示 write / query / part-sync 吞吐和 p99;**liaison → liaison** 边展示 write-forward 和 control;**lifecycle → data** 边展示 tier-migration volume / rate / p99。每条边最多内联显示三项属于该 pair 的指标,选中边面板保留完整 client-vs-server 拆分,**Flows** 子标签页则把每条边按 role-pair 展开成一组对齐表。  -图 6:Flows,把同样的 role-pair 边显示成可排序表格,每个 pair 一个区块。</br> +图 6:Flows,把这些 role-pair 边显示成可排序表格,每个 pair 一个区块。</br> 这里有两个前提。边和角色专属面板假设你运行的是一个真实的 **集群化** BanyanDB;单进程 standalone 实例只会显示共享资源和 Go runtime 面板,其余面板会在集群角色开始上报后亮起来。尤其是 container-to-container 边,还需要 OAP 构建暴露 `SERVICE_INSTANCE_RELATION` 作用域;在那之前,Deployment 标签页仍然会画出完整清单,只是 pod 之间没有边。 @@ -82,4 +82,4 @@ Deployment 标签页中,容器之间的调用边会从 SWIP-15 instance-relati 上面这些不是一张手工写死的 "BanyanDB 页面"。聚类规则、每个 role 的节点指标、role-pair 边指标,都在 Layer template 中作为一个自包含块存在,可以从 **Layer dashboards admin → Deployment** 作用域编辑,并随模板 export/import 一起携带。它和其他 Layer 背后的配置驱动模型一样,后续文章会完整展开。 -下一篇:**3D Infrastructure Map**。同一个部署,以及所有其他 Layer,会进入三维空间,变成一张展示系统全貌的 WebGL 视图。 +下一篇看 3D Infrastructure Map:把所有 Layer 的服务放进一张 WebGL 地图,从全局看部署状态。 diff --git a/content/zh/2026-06-21-horizon-ui-topology-and-dependency/index.md b/content/zh/2026-06-21-horizon-ui-topology-and-dependency/index.md index ecd7085193b..a3760fcfc76 100644 --- a/content/zh/2026-06-21-horizon-ui-topology-and-dependency/index.md +++ b/content/zh/2026-06-21-horizon-ui-topology-and-dependency/index.md @@ -10,17 +10,17 @@ tags: *译自英文原文:[Meet Horizon UI · 3/17: Topology & Service Dependency](/blog/2026-06-21-horizon-ui-topology-and-dependency/)。* -这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第三篇。[第二篇](/zh/2026-06-21-horizon-ui-dashboards-and-mqe/)讲的是如何从仪表盘数字里理解服务;这一篇讲如何把服务关系画成一张 **地图**:谁调用谁,调用有多重,以及同一个逻辑服务在不同上报 Layer 中是什么样子。 +这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第三篇。[第二篇](/zh/2026-06-21-horizon-ui-dashboards-and-mqe/)讲的是如何从仪表盘数字里理解服务;这一篇讲如何把服务关系画成一张 **地图**:谁调用谁,调用量有多大,以及同一个逻辑服务在不同上报 Layer 中是什么样子。 -SkyWalking 拓扑背后的调用数据是一份,但 Horizon 不只画一种图。它有每个 Layer 自己的服务地图,可以从单条调用下钻到背后的实例,可以看 endpoint 级依赖图,也可以用跨 Layer 叠加视图把一个服务在不同 Layer 中的形态连起来。它们共用同一套引擎,只是回答的问题不同。(Deployment 标签页,也就是单个集群服务 *内部* 实例的地图,以及 WebGL 3D 地图,都足够大,会在接下来的文章里单独讲。) +SkyWalking 拓扑背后的调用数据只有一份,但 Horizon 不只画一种图。它有每个 Layer 自己的服务地图,可以从单条调用下钻到背后的实例,可以看 endpoint 级依赖图,也可以用跨 Layer 叠加视图把一个服务在不同 Layer 中的形态连起来。它们共用同一套引擎,只是回答的问题不同。(Deployment 标签页,也就是单个集群服务 *内部* 实例的地图,以及 WebGL 3D 地图,都足够大,会在接下来的文章里单独讲。) ## 一套拓扑引擎,适配不同 Layer -打开任意 Layer 的 **Topology** 标签页,你会看到一张从左到右排列的层次化服务地图:`User` 存在时位于最左侧,每个服务按调用深度落在不同列里;同一列内部则保留图遍历到它们时的顺序,所以主调用链可以自上而下读。每个服务都是一个 **六边形节点**,节点上展示的所有东西都来自这个 Layer 的配置,没有写死逻辑: +打开任意 Layer 的 **Topology** 标签页,你会看到一张从左到右排列的层次化服务地图:`User` 存在时位于最左侧,每个服务按调用深度落在不同列里;同一列内部保留图遍历时的顺序,所以主调用链可以自上而下读。每个服务都是一个 **六边形节点**,节点上展示的所有东西都来自这个 Layer 的配置,没有写死逻辑: - 六边形 **边框** 承载节点的 **ring** 指标,用类似 SLA 的健康色带展示(绿色 → 红色); - 组件 **图标** 放在六边形内部,和 Trace 瀑布图使用同一套图标,所以 PostgreSQL 节点像 PostgreSQL,Kafka 节点像 Kafka; -- 节点头部数字,也就是 **center** 指标,带单位显示在六边形 **上方**; +- 节点的主数字,也就是 **center** 指标,带单位显示在六边形 **上方**; - 服务 **名称** 显示在节点 **下方**,**secondary** 指标(默认是时延)显示在名称下面; - 每条 **边** 都带调用吞吐的 **RPM 标记**,使用服务端指标,缺失时回退到客户端指标。 @@ -31,44 +31,44 @@ SkyWalking 拓扑背后的调用数据是一份,但 Horizon 不只画一种图 ## 过滤拓扑噪声 -真实拓扑通常很嘈杂。一张密集地图里会混入 OAP 没法完整识别的推测节点,比如裸露的 `rcmd:80`、未接入探针的地址。Horizon 的 **Filter** 控件可以关掉这些噪声,同时保留真实依赖。它会自动生成一个 **按 Layer 分组** 的过滤维度,展示方式和侧边栏一致。每一行都有 Layer 自己的图标和本地化名称,比如 *Virtual Database*、*Java Agent*,还会有一个 **Others** 分组,用来收纳 OAP 无法分类的节点,以及一个独立的 **User** 开关。取消勾选 *Others* 后,未接入探针的杂点和悬空边会消失,而数据库、缓存和队列(各自的 `VIRTUAL_*` 行)仍然留在图上。过滤在客户端执行,行会在每次刷新时重新推导,所以不会陈旧。 +真实拓扑通常很嘈杂。一张密集地图里会混入 OAP 没法完整识别的推测节点,比如裸露的 `rcmd:80`、未接入探针的地址。Horizon 的 **Filter** 控件可以关掉这些噪声,同时保留真实依赖。它会自动生成一个 **按 Layer 分组** 的过滤分面,展示方式和侧边栏一致。每一行都有 Layer 自己的图标和本地化名称,比如 *Virtual Database*、*Java Agent*,还会有一个 **Others** 分组,用来收纳 OAP 无法分类的节点,以及一个独立的 **User** 开关。取消勾选 *Others* 后,未接入探针的杂点和悬空边会消失,而数据库、缓存和队列(各自的 `VIRTUAL_*` 行)仍然留在图上。过滤在客户端执行,每次刷新都会重新推导分组选项,所以不会陈旧。  图 2:快速降噪,去掉无法解析的 "Others" 节点,同时保留真实依赖。</br> 当某个 Layer 的服务属于 OAP service group,地图上的服务聚焦选择器也会按这些 group 分组。点击 group header 可以 **批量选中或清空该组里的所有服务**,所以你能一次聚焦一张繁忙地图中某个团队负责的那一片。 - -图 3:从地图选择器里一次聚焦整个 service group。</br> + +图 3:支持 service group 的服务选择器,可以一次聚焦整个分组。</br> ## 从服务调用下钻到实例关系 -服务到服务的边是一条聚合调用;它背后是真实实例在和真实实例通信。点击地图上的一条调用并选择 **Instance map →**,Horizon 会画出这层关系:客户端服务的实例在左列,服务端服务的实例在右列,中间是实例级调用,并带有客户端→服务端方向动画。它复用服务地图上的所有能力:健康 ring 节点、每条调用的 client/server 指标侧栏、带 **Open instance dashboard** 的节点 popover,并且按 Layer 自己的词汇标注列名,比如 Kubernetes 上叫 *Pods*,data plane 上叫 *Sidecars*。两个服务选择器会根据关系联动:server 列表来自当前 client 的 callees,client 列表来自当前 server 的 callers;每次改动其中一个,另一个都会重新推导。 +服务到服务的边是一条聚合调用;它背后是真实实例之间的通信。点击地图上的一条调用并选择 **Instance map →**,Horizon 会画出这层关系:客户端服务的实例在左列,服务端服务的实例在右列,中间是实例级调用,并带有客户端→服务端方向动画。它复用服务地图上的所有能力:健康 ring 节点、每条调用的 client/server 指标侧栏、带 **Open instance dashboard** 的节点 popover,并且按 Layer 自己的词汇标注列名,比如 Kubernetes 上叫 *Pods*,data plane 上叫 *Sidecars*。两个服务选择器会根据关系联动:server 列表来自当前 client 的 callees,client 列表来自当前 server 的 callers;每次改动其中一个,另一个都会重新推导。  图 4:从一条聚合调用下钻到背后的实例到实例流量。</br> ## 按 endpoint 追完整请求链 -服务拓扑回答“哪些服务调用了这个服务”。**API dependency** 标签页回答更具体的问题:“哪些 *endpoint* 调用了这个 endpoint,它又调用了哪些 endpoint”。选择一个 endpoint 后,图会按方向分列:callers 在左,焦点 endpoint 在中间,callees 在右。它同样用 SLA 色边框、每条边上的 RPM 和时延,以及最重边标签。选中节点后会出现一个 **+** handle,点击后拉入 *它自己* 的 callers 和 callees。这样你可以一跳一跳追链路,不需要一次展开整张图。拖开节点后,外跳链接(**Open endpoint**、**Service →**)会在新标签页打开,保留你正在探索的图。 +服务拓扑回答“哪些服务调用了这个服务”。**API dependency** 标签页回答更具体的问题:“哪些 *endpoint* 调用了这个 endpoint,它又调用了哪些 endpoint”。选择一个 endpoint 后,图会按方向分列:callers 在左,焦点 endpoint 在中间,callees 在右。它同样用 SLA 色边框、每条边上的 RPM 和时延,以及最重边标签。选中节点后会出现一个 **+** handle,点击后拉入 *它自己* 的 callers 和 callees。这样你可以一跳一跳追链路,不需要一次展开整张图。把节点拖开后,跳转链接(**Open endpoint**、**Service →**)会在新标签页打开,当前正在探索的图仍然保留。  图 5:按 endpoint 一跳一跳走请求链,每条边都有时延。</br> ## 同一个服务在不同 Layer 中的形态 -一个逻辑服务经常会同时通过多个 Layer 上报:General agent、Service Mesh sidecar、mesh data plane、Kubernetes pod。SkyWalking 从 OAP 10 开始就建模了这种跨 Layer 层级关系;Horizon 做的事情,是让你能从地图上的任何位置直接打开它。选中一个节点后,Horizon 会惰性探测它的 hierarchy。如果这个服务有跨 Layer 对应对象,节点上会贴一个小的 **chevron-stack** 标记。 +一个逻辑服务经常会同时通过多个 Layer 上报:General agent、Service Mesh sidecar、mesh data plane、Kubernetes pod。SkyWalking 从 OAP 10 开始就建模了这种跨 Layer 层级关系;Horizon 把这层关系放进了地图交互。选中一个节点后,它会按需探测 hierarchy。如果这个服务有跨 Layer 对应对象,节点上会贴一个小的 **chevron-stack** 标记。  -图 6:选中节点上的 chevron-stack 标记。它在选择时惰性探测,只在服务存在跨 Layer 对应对象时显示。</br> +图 6:选中节点上的 chevron-stack 标记。它在选择时按需探测,只在服务存在跨 Layer 对应对象时显示。</br> -点击这个标记,拓扑会在 **Smartscape** 叠加视图下变暗:焦点节点在原位高亮重绘,它的对应对象按 OAP 的 Layer 顺序纵向展开,请求近端 Layer 在上,基础设施近端 Layer 在下。之后两步点击就能在对应 Layer 中打开任意对象,并完成预选。(叠加视图打开时自动刷新会暂停,避免内容在你眼前移动。) +点击这个标记,拓扑会在 **Smartscape** 叠加视图下变暗:焦点节点在原位高亮重绘,它的对应对象按 OAP 的 Layer 顺序纵向展开,靠近请求入口的 Layer 在上,靠近基础设施的 Layer 在下。之后两步点击就能在对应 Layer 中打开任意对象,并完成预选。(叠加视图打开时自动刷新会暂停,避免内容在你眼前移动。)  -图 7:一个服务在所有上报 Layer 中的样子,通过叠加视图展示跨 Layer hierarchy。</br> +图 7:一个服务在所有上报 Layer 中的样子,用叠加视图展示跨 Layer 层级关系。</br> ## 后续阅读 这些地图上的每个指标、阈值和边权重,都位于 Layer template 的 `topology` 块里。换句话说,你会用和仪表盘一样的配置驱动方式调整它们,这也是后续文章的主题。字段参考可以看 [layer-template](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/customization/layer-templates/) 里的 topology 文档。 -下一篇:**Deployment 标签页和 BanyanDB 自观测**。同样的地图技术会用到服务 *内部*,展示一个集群服务自身实例是如何部署、如何相互通信的。 +下一篇转向服务内部:Deployment 标签页如何展示集群服务的实例关系,以及 BanyanDB 如何接入 SkyWalking 自观测。 diff --git a/content/zh/2026-06-21-skywalking-horizon-ui-introduction/index.md b/content/zh/2026-06-21-skywalking-horizon-ui-introduction/index.md index 8b523c3dea7..4a9d0ec0a3f 100644 --- a/content/zh/2026-06-21-skywalking-horizon-ui-introduction/index.md +++ b/content/zh/2026-06-21-skywalking-horizon-ui-introduction/index.md @@ -14,20 +14,20 @@ Apache SkyWalking Horizon UI 是 SkyWalking 的新一代 Web 控制台。它仍 这是这个系列的第一篇。后续文章会依次介绍仪表盘和指标查询语言、拓扑视图、整个部署的 WebGL 3D 地图、链路和日志探索器、性能剖析、运维界面、访问控制,以及把这些模块串起来的配置化定制。本篇先交代背景:Horizon 是什么,整个 UI 围绕哪一个核心想法构建,以及今天如何把它接到你的 OAP 前面。 -Horizon 的主线可以概括为四件事:先 **observe**,看拓扑、链路、日志、五类性能剖析、只读告警,以及每个 Layer 的仪表盘;再 **operate** 这些被观测对象;然后 **govern** 谁可以操作它们;最后在不写 UI 代码的情况下 **customize** 整个控制台。Observe、operate、govern、customize,就是这个系列的主线。我们从每次会话都会看到的地方开始:侧边栏。 +Horizon 的主线可以概括为四个动词:先 **observe**,看拓扑、链路、日志、五类性能剖析、只读告警,以及每个 Layer 的仪表盘;再 **operate** 这些被观测对象;然后 **govern** 谁可以操作它们;最后在不写 UI 代码的情况下 **customize** 整个控制台。Observe、operate、govern、customize,就是这个系列的主线。我们从每次会话都会看到的地方开始:侧边栏。 ## 侧边栏显示当前系统全貌 -打开 Horizon,左侧边栏不是手工写死的菜单,而是 OAP 当前上报内容的实时映射。Horizon 会向 OAP 查询有哪些 Layer、哪些 Layer 里有服务,然后只渲染这些内容,并每 60 秒刷新一次。某个 Layer 开始上报,它就出现;安静下来,它就消失。菜单不会和真实状态脱节,因为它直接来自 OAP 当前状态。 +打开 Horizon,左侧边栏不是手工写死的菜单,而是 OAP 当前上报内容的实时映射。Horizon 会向 OAP 查询有哪些 Layer、哪些 Layer 里有服务,然后只渲染这些内容,并每 60 秒刷新一次。某个 Layer 开始上报,它就出现;不再上报,它就消失。菜单不会和真实状态脱节,因为它直接来自 OAP 当前状态。  图 1:Horizon 首页,左侧是实时系统全貌,右侧是跨 Layer 的 Services 概览。</br> 这个侧边栏有几个关键点: -- **实时服务计数。** **Layers** 标题显示当前有多少个 Layer 包含服务,图 1 中是 *13 with services*。展开每个 Layer 后,也能看到它自己的服务数量。这些计数来自同一个服务端目录,整个 UI 共用,并且每分钟刷新一次,所以侧边栏、告警 Layer 标记器和落地页不会因为各自轮询而出现不一致。 -- **按组组织,而不是平铺列表。** Layer 会放在自己的分组下,比如 *Virtual Targets*、*Istio*、*Kubernetes*、*MQ*。顶部是 **Overviews** 和 **Alarms**,运维和管理区域(Cluster Status、Alerting rules、DSL Management、Users、Roles & permissions)放在更靠下的位置,并且只对有权限的角色展示。访问控制直接进入菜单生成逻辑,而不是事后再补一层。 -- **不会静默隐藏内容。** OAP 上报的每个 Layer 现在都会出现。即使没有内置模板,也会回退到一个普通的 Service 页面。过去有一份写死的 "hidden layers" 列表,会悄悄隐藏 `BanyanDB` 这样的 Layer;现在这件事被移除了。想隐藏某个 Layer,需要在 `horizon.yaml` 里通过 `layers.excluded` 明确配置。默认值是 `FAAS` 和 `VIRTUAL_GATEWAY`;清空列表就可以展示所有 Layer。 +- **实时服务计数。** **Layers** 标题显示当前有多少个 Layer 包含服务,图 1 中是 *13 with services*。展开每个 Layer 后,也能看到它自己的服务数量。这些计数来自同一个服务端目录,整个 UI 共用,并且每分钟刷新一次,所以侧边栏、告警里的 Layer 标记和落地页不会因为各自轮询而出现不一致。 +- **按组组织,而不是平铺列表。** Layer 会放在自己的分组下,比如 *Virtual Targets*、*Istio*、*Kubernetes*、*MQ*。顶部是 **Overviews** 和 **Alarms**,运维和管理区域(Cluster Status、Alerting rules、DSL Management、Users、Roles & permissions)放在更靠下的位置,并且只对有权限的角色展示。菜单生成时就会应用访问控制,而不是先渲染出来再补一层拦截。 +- **不会静默隐藏内容。** OAP 上报的每个 Layer 现在都会出现。即使没有内置模板,也会回退到一个普通的 Service 页面。过去有一份写死的 "hidden layers" 列表,会悄悄隐藏 `BanyanDB` 这样的 Layer;现在不再有这层隐藏逻辑。想隐藏某个 Layer,需要在 `horizon.yaml` 里通过 `layers.excluded` 明确配置。默认值是 `FAAS` 和 `VIRTUAL_GATEWAY`;清空列表就可以展示所有 Layer。 点击任意 Layer,Horizon 会打开它的第一个可用标签页。所有 Layer 都沿用同一条固定路径: @@ -44,10 +44,10 @@ service → instance → endpoint → topology → trace → logs → profiling 一个跟随实时数据变化的控制台,必须能处理“没有数据”的时刻:全新安装、配置不完整的部署,或者刚刚重启的 OAP。Horizon 把这些情况都作为明确状态处理,而不是留给用户一个死胡同。 -打开 `/` 时,Horizon 会按顺序跳到一个真实可用的页面:第一个可用的公共 overview 仪表盘;如果没有,就进入第一个有服务的 Layer;只有两者都不存在时,才展示空落地页。真的进入空页面时,它会用明确语言告诉你问题在哪里:**"No data is flowing yet"** 表示还没有任何内容上报,**"No dashboard configured yet"** 表示已经有服务,但没有配置 overview。它会把问题指向数据接入或运维配置,而不是把你丢在一个空网格上。只要有服务开始上报,或者运维人员发布了仪表盘,下一次 60 秒刷新就会把空页面替换成真实页面。 +打开 `/` 时,Horizon 会按顺序跳到一个真实可用的页面:第一个可用的公共 overview 仪表盘;如果没有,就进入第一个有服务的 Layer;只有两者都不存在时,才展示空状态页。确实没有可跳转页面时,它会明确说明问题在哪里:**"No data is flowing yet"** 表示还没有任何内容上报,**"No dashboard configured yet"** 表示已经有服务,但没有配置 overview。它会把问题指向数据接入或运维配置,而不是把你丢在一个空网格上。只要有服务开始上报,或者运维人员发布了仪表盘,下一次 60 秒刷新就会把空状态替换成真实页面。 - -图 3:空落地页说明真实原因。这里是服务已经上报,但没有配置 overview,而不是给出一个空仪表盘。</br> + +图 3:空状态页说明真实原因。这里是服务已经上报,但没有配置 overview,而不是给出一个空仪表盘。</br> OAP 短暂不可达时,Horizon 也遵循同样思路。如果后端短时间连不上,Horizon 会保留最后一次已知的侧边栏结构,并显示 "OAP unreachable" 横幅,服务计数标记为未知,直到恢复为止。短暂故障不会看起来像配置突然消失。 @@ -63,12 +63,12 @@ OAP 短暂不可达时,Horizon 也遵循同样思路。如果后端短时间 ## BFF:Horizon 的服务端入口 -过去,SkyWalking Web UI 是浏览器直接访问 OAP。Horizon 在中间加了一小层基础设施:**Backend-for-Frontend (BFF)**,一个运行在 Node.js 上的 Fastify 服务。它负责提供 UI,并代理所有到 OAP 的调用。 +过去,SkyWalking Web UI 是浏览器直接访问 OAP。Horizon 在浏览器和 OAP 之间引入了一个服务端组件:**Backend-for-Frontend (BFF)**,一个运行在 Node.js 上的 Fastify 服务。它负责提供 UI,并代理所有到 OAP 的调用。  -*浏览器只访问 BFF;BFF 负责 auth、RBAC、audit、capability probing,以及服务端 i18n / widget gating,然后代理到 OAP 的 query host(`:12800`)和 admin host(`:17128`)。* +*浏览器只访问 BFF;BFF 负责认证、RBAC、审计、能力探测,以及服务端 i18n / 组件开关,然后代理到 OAP 的 query host(`:12800`)和 admin host(`:17128`)。* -后续文章里很多能力都依赖这一层。认证、基于角色的访问控制和审计都在服务端执行,伪造请求绕不过去。BFF 启动时会探测一次 OAP 的 GraphQL schema,某个能力不存在时就优雅降级;Horizon 能用同一个构建支持两代 OAP,靠的就是这个机制。它还会每分钟缓存一次服务目录,让整个 UI 对系统全貌持有同一份视图。运维、安全和定制相关的文章会分别展开这些内容;这里先记住一点:现在这里有一个服务端,它承担了实实在在的工作。 +后续文章里很多能力都依赖这一层。认证、基于角色的访问控制和审计都在服务端执行,伪造请求绕不过去。BFF 启动时会探测一次 OAP 的 GraphQL schema,某个能力不存在时就优雅降级;Horizon 能用同一个构建支持两代 OAP,靠的就是这个机制。它还会每分钟缓存一次服务目录,让整个 UI 共享同一份系统视图。运维、安全和定制相关的文章会分别展开这些内容;这里先记住一点:UI 前面现在有了一个真正做事的服务端。 ## 用 3D 地图查看完整部署 @@ -76,7 +76,7 @@ OAP 短暂不可达时,Horizon 也遵循同样思路。如果后端短时间 {{< map3d poster="/images/home/horizon-3d-map.png" badge="交互演示 · 示例数据" >}} -后续有一篇专门拆解 3D 地图:层级、告警信标、让健康对象变暗、只突出告警对象的 "Beacon mode",以及配置它的结构化编辑器。现在先把它当成 Horizon 目标的一个缩影:整个部署放在一个视图里,而且是活的。 +后续有一篇专门拆解 3D 地图:层级、告警信标、让健康对象变暗、只突出告警对象的 "Beacon mode",以及用来配置它的结构化编辑器。现在先把它当成 Horizon 目标的一个缩影:整个部署放在一个视图里,而且是活的。 ## 系列后续内容 @@ -90,7 +90,7 @@ OAP 短暂不可达时,Horizon 也遵循同样思路。如果后端短时间 4. **The 3D Infrastructure Map**:详细展开上面这个 3D 视图。 5. **Trace explorer**:在时延散点图上框选慢 Trace,然后用三种方式阅读同一条 Trace。 6. **Log explorer**:类似 Loki 的日志流,带 facets、top patterns 和结构化内容。 -7. **Browser & RUM monitoring**:前端错误日志,以及把混淆后的栈恢复到原始源码行。 +7. **Browser & RUM monitoring**:前端错误日志,以及把压缩后的 stack 还原到原始源码行。 8. **Five profilers, one flame graph**:trace、async-profiler、eBPF、Go pprof 和 network profiling,统一到同一套工作方式里。 **运维** @@ -140,7 +140,7 @@ auth: 打开 `http://<host>:8081/`,登录后第一站是 **Cluster Status**,确认 Horizon 和 OAP 能正常通信。之后侧边栏就会填入你的系统全貌。 -完整安装路径,包括 binary tarball、Kubernetes、LDAP、TLS 和生产检查清单,请看 [Horizon UI 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/readme/)。左侧菜单里覆盖了安装、兼容性、访问控制、定制、组件和运维。 +完整安装路径,包括 binary tarball、Kubernetes、LDAP、TLS 和生产检查清单,请看 [Horizon UI 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/readme/)。文档左侧菜单覆盖安装、兼容性、访问控制、定制、组件和运维。 ## 其他要点 @@ -149,4 +149,4 @@ auth: - **基于现代技术栈。** 前端是 Vue 3 + TypeScript on Vite、Pinia、Apache ECharts、D3 和 Monaco;BFF 使用 Node.js 上的 Fastify。 - **Apache 许可证,社区共建。** Horizon UI 位于 [apache/skywalking-horizon-ui](https://github.com/apache/skywalking-horizon-ui)。欢迎接到你的集群上试用,也欢迎告诉我们缺什么,issue 和 pull request 都可以。 -下一篇:仪表盘,以及为什么一个组件可以在服务端判断自己不适用于当前对象,并且连查询都不发。 +下一篇讲仪表盘:MQE 如何驱动组件,BFF 又如何在服务端判断哪些组件该显示、哪些查询可以直接省掉。 diff --git a/content/zh/2026-06-22-horizon-ui-3d-infrastructure-map/index.md b/content/zh/2026-06-22-horizon-ui-3d-infrastructure-map/index.md index 6c9321e3e33..5e5544b35dd 100644 --- a/content/zh/2026-06-22-horizon-ui-3d-infrastructure-map/index.md +++ b/content/zh/2026-06-22-horizon-ui-3d-infrastructure-map/index.md @@ -12,39 +12,39 @@ tags: 这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第五篇。[第三篇](/zh/2026-06-21-horizon-ui-topology-and-dependency/)画的是服务 *之间* 的地图,[第四篇](/zh/2026-06-21-horizon-ui-deployment-and-banyandb/)画的是单个服务 *内部* 的地图。这一篇把视角拉到最远:用一个 **WebGL** 视图一次看完整个部署。每个 SkyWalking Layer 的服务都会渲染成立方体,堆叠到 3D 空间里,并显示实时流量、告警和它们之间的调用关系。它补上了按 Layer 仪表盘之外的全局视角:退后一步,看整个系统。 -它也确实可以交互。所以这里直接放出来:下面就是运行在 demo 示例数据上的真实地图。拖动旋转,滚轮缩放,点击立方体: +而且它不是静态截图。所以这里直接放出来:下面就是运行在 demo 数据上的真实地图。拖动旋转,滚轮缩放,点击立方体: {{< map3d poster="/images/home/horizon-3d-map.png" badge="交互演示 · 示例数据" >}} ## 一张 3D 图查看完整部署 -3D 地图是 `/3d/map` 里的独立全屏页面,从顶栏里的 **3D Infra** 入口打开。它刻意拿掉控制台其余部分:没有侧边栏,没有顶栏,没有全局时间选择器。整个 viewport 都交给场景。SkyWalking 标识放在左下角,右上角的 `×` 返回 Horizon。里面所有数据都来自 Horizon 其他页面访问的同一个 OAP:服务清单、每个 Layer 的拓扑、每个服务的流量,以及活跃告警。 +3D 地图是 `/3d/map` 里的独立全屏页面,从顶栏里的 **3D Infra** 入口打开。它刻意拿掉控制台其余部分:没有侧边栏,没有顶栏,没有全局时间选择器。整个浏览器视口都交给场景。SkyWalking 标识放在左下角,右上角的 `×` 返回 Horizon。里面所有数据都来自 Horizon 其他页面访问的同一个 OAP:服务清单、每个 Layer 的拓扑、每个服务的流量,以及活跃告警。  图 1:一张 3D 图看完整部署:服务是立方体,Layer 是带颜色的 zone,角色按 tier 堆叠。</br> ## 用 tier 组织系统层次 -**Tier** 是一层横向平面,用来把系统中职责相近的 Layer 放在一起。Tier 从上到下的阅读顺序,就是请求流动的大致方向:从用户触达的应用,一路到底层平台。Horizon 内置四个 tier: +**Tier** 是一层横向平面,用来把系统中职责相近的 Layer 放在一起。Tier 从上到下的阅读顺序,就是请求流动的大致方向:从用户直接访问的应用,一路到底层平台。Horizon 内置四个 tier: - **Apps**(顶部):应用界面和应用视角看到的依赖,包括 General agent services、Browser/RUM、mobile,以及 `Virtual*` targets(database、cache、MQ、gateway、GenAI)。 - **Middleware**:数据和消息服务、网关,以及自观测组件,包括 MySQL、PostgreSQL、Redis、Kafka、RocketMQ、APISIX、Nginx、SkyWalking SO11Y components 和云托管数据服务。 - **Service Mesh**:承载应用流量的 mesh,包括 Istio control/data plane、Cilium、Envoy AI Gateway。 - **Infra**(底部):其余内容运行在其上的平台,包括 Kubernetes、hosts、VMs。 -OAP 上报的每个 Layer 都会归入一个 tier。Horizon 还没分类的新 Layer,比如 OAP 新增的 Layer,会归入 **failover tier**(默认 Middleware)并带 "unclassified" 标记。这样它会出现,运维人员也可以重新分配,而不是静默从地图上消失。右侧面板映射了整个堆栈:点击 tier 行可以把镜头移到对应位置,用眼睛开关一次显示或隐藏整个 tier(或单个 Layer),并查看当前可见服务数量。 +OAP 上报的每个 Layer 都会归入一个 tier。Horizon 还没分类的新 Layer,比如 OAP 新增的 Layer,会归入 **failover tier**(默认 Middleware)并带 "unclassified" 标记。这样它会出现,运维人员也可以重新分配,而不是静默从地图上消失。右侧面板对应整个堆栈:点击 tier 行可以把镜头移到对应位置,用眼睛开关一次显示或隐藏整个 tier(或单个 Layer),并查看当前可见服务数量。 ## 地图元素:立方体、zone 和流量 每个服务对应一个 **立方体**。立方体会在 tier 上按所属 Layer 聚成一个 **zone**:半透明矩形使用该 Layer 的品牌色,并盖上项目 logo(Istio 的帆、Kubernetes 舵轮、数据库圆柱、队列图标),所以从任何视角都能辨认出 zone。带拓扑的 Layer(General、Service Mesh、Kubernetes Service、Cilium)会按 **调用依赖** 摆放立方体:上游 caller 在一侧,下游服务在另一侧,对应 2D 服务地图的 3D 版本。没有拓扑的 Layer 则把立方体排成整齐网格。 -立方体下方的小 **traffic** 标签显示该服务的实时主吞吐:应用和 mesh 服务是 requests per minute,数据服务是 queries 或 operations per second,并保留各自单位。只有足够近、可读时标签才出现;缩远后会淡出,保持场景干净;选中的立方体总会显示它的数字。 +立方体下方的小 **traffic** 标签显示该服务的实时主吞吐:应用和 mesh 服务是 requests per minute,数据服务是 queries 或 operations per second,并保留各自单位。只有镜头足够近、文字可辨认时标签才出现;缩远后会淡出,保持场景干净;选中的立方体总会显示它的数字。 ## 用 Beacon mode 突出告警服务 当一个服务有 **当前正在触发的告警** 时(Horizon 轮询最近 20 分钟,并只统计 service 作用域下仍在触发的告警),它的立方体会 **红色脉冲**。这就是一个信标,即使隔着整个场景也能看到,而告警列表会独立刷新。 -在繁忙地图里,几百个立方体中的一个红点仍然可能难找,所以有 **Beacon mode**。从工具栏打开后,所有 *健康* 立方体都会变成深色 wireframe,只留下正在告警的服务发光。部署结构仍然清楚,但真正出问题的服务才有颜色。这个模式可以把鸟瞰图快速切成 incident triage 界面。 +在繁忙地图里,几百个立方体中的一个红点仍然可能难找,所以有 **Beacon mode**。从工具栏打开后,所有 *健康* 立方体都会变成深色 wireframe,只留下正在告警的服务发光。部署结构仍然清楚,但真正出问题的服务才有颜色。这个模式可以把鸟瞰图快速切成 incident triage 视图。  图 2:Beacon mode 会把健康对象变暗成幽影,所以你只会看到正在触发的服务。</br> @@ -55,10 +55,10 @@ OAP 上报的每个 Layer 都会归入一个 tier。Horizon 还没分类的新 L - **In-layer calls**:同一个 Layer 内两个服务之间的浅青色管线,并带沿线运动的数据包动画。这是每个 Layer 自己的内部调用图,始终开启。 - **Cross-layer calls**:同一个 tier 上不同 Layer 服务之间的柔和琥珀色箭头,比如 Browser app 调用 Frontend、Frontend 调用 Virtual Database,方向从 caller 指向 callee。 -- **Hierarchy links**:这是让 3D 布局真正发挥价值的一类线。选中一个立方体后,粗灰色管线会连接 **同一个逻辑服务在不同 tier 上的多张面孔**:agent 看到的服务、mesh 看到的服务、Kubernetes service 看到的服务。它们表示 *身份*,不是流量,所以默认隐藏;选中立方体后只显示与它相关的对象,并沿着 tier 一层层爬上去。这就是[第三篇](/zh/2026-06-21-horizon-ui-topology-and-dependency/)里的 Smartscape,用它本来就该拥有的维度画出来。 +- **Hierarchy links**:这是让 3D 布局真正发挥价值的一类线。选中一个立方体后,粗灰色管线会连接 **同一个逻辑服务在不同 tier 上的不同形态**:agent 看到的服务、mesh 看到的服务、Kubernetes service 看到的服务。它们表示 *身份*,不是流量,所以默认隐藏;选中立方体后只显示与它相关的对象,并沿着 tier 一层层爬上去。这就是[第三篇](/zh/2026-06-21-horizon-ui-topology-and-dependency/)里的 Smartscape,只是放到了更适合表达层级关系的 3D 视角里。  -图 3:选中立方体后,身份链接沿 tier 爬升:同一个服务,被 agent、mesh 和 Kubernetes 分别看到。</br> +图 3:选中立方体后,身份链接沿 tier 爬升:agent、mesh 和 Kubernetes 各自看到的同一个服务。</br> ## 视角移动与选择 @@ -66,13 +66,13 @@ OAP 上报的每个 Layer 都会归入一个 tier。Horizon 还没分类的新 L ## 地图如何分阶段加载 -整个部署的数据太大,不适合一次请求拉完,所以地图分阶段加载,底部细长的 **timeline strip** 会实时展示进度:**Services**(服务清单和所属 Layer)→ **Templates**(哪些 Layer 带拓扑)→ **Topologies**(每个有拓扑 Layer 的调用图)→ **Hierarchy**(跨 tier 身份链接)→ **Layout**(摆放立方体)→ **Metrics**(按批次获取每个服务的流量,让立方体逐步亮起来)。点击任意阶段可以打开抽屉查看详情,也可以点击 refresh 重新跑完整流程。 +整个部署的数据太大,不适合一次请求拉完,所以地图分阶段加载,底部细长的 **timeline strip** 会实时展示进度:**Services**(服务清单和所属 Layer)→ **Templates**(哪些 Layer 带拓扑)→ **Topologies**(每个有拓扑 Layer 的调用图)→ **Hierarchy**(跨 tier 身份链接)→ **Layout**(摆放立方体)→ **Metrics**(按批次获取每个服务的流量,让立方体逐步亮起来)。点击任意阶段可以打开抽屉查看详情,也可以点击 Refresh 重新跑完整流程。 -两个设计让刷新变便宜。**Hierarchy** 阶段是增量的:只有上次之后新增的服务需要探测,其余从缓存复用,所以稳定部署在这一步没有额外代价。场景还会按每个 Layer 的结构 hash 更新 key;结构没变时刷新会保留你的视角位置,只有服务清单或边真的变化时才重建布局。底层使用 [Three.js](https://threejs.org/) 加一层很薄的 Vue wrapper,同类立方体共享 geometry 和 material。正是这些细节,让几百个服务也能在浏览器标签页里平滑渲染。 +两个设计降低了刷新成本。**Hierarchy** 阶段是增量的:只有上次之后新增的服务需要探测,其余从缓存复用,所以稳定部署在这一步没有额外代价。场景还会按每个 Layer 的结构 hash 重新生成 key;结构没变时刷新会保留你的视角位置,只有服务清单或边真的变化时才重建布局。底层使用 [Three.js](https://threejs.org/) 加一层很薄的 Vue 封装,同类立方体共享 geometry 和 material。正是这些细节,让几百个服务也能在浏览器标签页里平滑渲染。 ## 地图结构来自配置 -上面这些不是一张写死的 "3D screen"。地图展示什么,由管理员在 `/admin/3d-map` 的 **结构化表单编辑器** 里编辑:tier、Layer、颜色和指标都通过表单控制,而不是直接写 JSON。你可以在里面: +上面这些不是一张写死的 "3D 页面"。地图展示什么,由管理员在 `/admin/3d-map` 的 **结构化表单编辑器** 里编辑:tier、Layer、颜色和指标都通过表单控制,而不是直接写 JSON。你可以在里面: - **用一个全局正则过滤 Layer**:匹配排除的内容会完全从地图上消失。 - **安排 tier**:重命名、从上到下重排,并把每个 Layer 固定到某个 tier 上,同时指定 failover tier,避免内容静默丢失。 @@ -88,4 +88,4 @@ Horizon 自带一份默认配置,所以地图开箱就有用。你的修改会 3D 地图是全局入口;2D 的按 Layer 页面仍然是最完整的服务地图。查看它只需要读权限(`infra-3d:read`,内置 viewer 角色及以上持有);调整它需要和仪表盘相同的写权限。字段参考,包括 tier、配置结构和加载阶段,可以看 [3D Infrastructure Map 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/operate/infra-3d-map/)。 -下一篇:**Trace Explorer**。从整个部署的鸟瞰图,回到单个请求,并用三种方式画出来。 +下一篇回到单个请求:Trace Explorer 会用分布图、瀑布图和调用树帮你定位慢调用。 diff --git a/content/zh/2026-06-22-horizon-ui-trace-explorer/index.md b/content/zh/2026-06-22-horizon-ui-trace-explorer/index.md index 00a8c40b161..b590867d45e 100644 --- a/content/zh/2026-06-22-horizon-ui-trace-explorer/index.md +++ b/content/zh/2026-06-22-horizon-ui-trace-explorer/index.md @@ -2,7 +2,7 @@ title: "认识 Horizon UI · 6/17:Trace 探索器" date: 2026-06-22 author: 吴晟 -description: "Horizon UI 系列第六篇:按 Layer 使用的分布式 Trace 探索器:先配置条件再查询,在时延分布图上框选异常 Trace,并用瀑布图、调用树和统计表阅读同一条 Trace。" +description: "Horizon UI 系列第六篇:位于 Layer 内的分布式 Trace 探索器:先配置条件再查询,在时延分布图上框选异常 Trace,并用瀑布图、调用树和统计表阅读同一条 Trace。" tags: - Tracing - Cloud Native @@ -10,17 +10,17 @@ tags: *译自英文原文:[Meet Horizon UI · 6/17: The Trace Explorer](/blog/2026-06-22-horizon-ui-trace-explorer/)。* -这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第六篇。前几篇都在讲地图:服务之间的[拓扑](/zh/2026-06-21-horizon-ui-topology-and-dependency/)、单个服务内部的 [deployment](/zh/2026-06-21-horizon-ui-deployment-and-banyandb/),以及整个系统全貌的 [3D view](/zh/2026-06-22-horizon-ui-3d-infrastructure-map/)。它们回答“我的系统长什么样”。这一篇把视角缩到 *一个请求*,看它的 spans、时序,以及到底是哪一跳变慢了。这就是 **Traces** 标签页。 +这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第六篇。前几篇都在讲地图:服务之间的[拓扑](/zh/2026-06-21-horizon-ui-topology-and-dependency/)、单个服务内部的 [Deployment 标签页](/zh/2026-06-21-horizon-ui-deployment-and-banyandb/),以及整个系统全貌的 [3D 视图](/zh/2026-06-22-horizon-ui-3d-infrastructure-map/)。它们回答“我的系统长什么样”。这一篇把视角缩到 *一个请求*,看它的 spans、时序,以及到底是哪一跳变慢了。这就是 **Traces** 标签页。 ## 面向排查,不做实时滚动刷新 Traces 标签页是一个位于 **Layer 内部** 的分布式 Trace 探索器:选择服务,设置条件,然后阅读单条 Trace 的 span 时间线。它有意和控制台其他部分不一样,因为 Trace 是排查数据,不是实时流。 -它使用 **独立的时间范围和条件**。它不跟随全局顶栏时间选择器,也不会自动刷新。你先配置要找的条件,再按 **Run query**;按之前不会取任何数据。第一次运行前,列表只显示 *"Pick your conditions, then click Run query."*。当你在追二十分钟前的一条坏 Trace 时,最不需要的就是窗口每几秒自动往前滚,所以它不会这么做。 +它使用 **独立的时间范围和条件**。它不跟随全局顶栏时间选择器,也不会自动刷新。你先配置要找的条件,再按 **Run query**;点击之前不会取任何数据。第一次运行前,列表只显示 *"Pick your conditions, then click Run query."*。当你在追二十分钟前的一条坏 Trace 时,最不需要的就是窗口每几秒自动往前滚,所以它不会这么做。 ## 用表单配置条件,不写查询语言 -过滤条件全部通过结构化表单配置:select、数字范围、tag 标签。条件会暂存在工具栏里,只在点击 **Run query** 后生效: +过滤条件全部通过结构化表单配置:select、数字范围、tag。条件会暂存在工具栏里,只在点击 **Run query** 后生效: - **Instance** 和 **Endpoint**:在服务内部收窄范围,endpoint 下拉框只列出这个服务自己的 endpoints。 - **Status**:`ALL` / `SUCCESS` / `ERROR`。 @@ -29,17 +29,17 @@ Traces 标签页是一个位于 **Layer 内部** 的分布式 Trace 探索器: - **Time range**:滚动预设(最近 15 分钟到 24 小时)或自定义绝对窗口,按 **秒级精度** 计算,所以刚结束的 Trace 不会因为分钟取整而掉出窗口。 - **Trace ID**:粘贴一个 id 直接查。 - **Duration range**:毫秒级 min-max。 -- **Tag**:自由输入 span tags,格式为 `key=value`(比如 `http.status_code=500`),按 Enter 添加为可删除标签,多个 tag 按 AND 连接;key 和 value 都从后端获得 typeahead。 +- **Tag**:自由输入 span tags,格式为 `key=value`(比如 `http.status_code=500`),按 Enter 添加为可删除标签,多个 tag 按 AND 连接;key 和 value 都由后端提供自动补全。 它不是查询语言。Horizon 里没有 TraceQL 输入框。上面的结构化条件就是完整界面。(TraceQL 是另一条路径:SkyWalking 后端可以通过 TraceQL 向 Grafana 提供 Trace,这件事在[另一篇文章](/zh/2026-04-08-traceql/)里讲。Horizon 的探索器是表单,不是 DSL。) ## 可框选的时延分布图 -结果返回后,工具栏下会出现一张 **Distribution** 图:每个点代表一条 Trace,X 轴是开始时间,高度是 duration,越慢的 Trace 越高。点按 **status** 着色:错误为红色,成功为强调色。所以左上角、右上角那类高处红点,就是你要找的“又慢又失败”的区域。 +结果返回后,工具栏下会出现一张 **Distribution** 图:每个点代表一条 Trace,X 轴是开始时间,高度是耗时,越慢的 Trace 越高。点按 **status** 着色:错误为红色,成功为强调色。所以左上角、右上角那类高处红点,就是你要找的“又慢又失败”的区域。 -这张图本身也是过滤器。点击一个点可以选中它,或者 **拖一个矩形框选一片点**,结果列表会收窄到这批选择;标题区切换成 *"N picked"* 计数,并提供 Reset。这是基于已加载结果的客户端过滤,不会发起新查询。直接框住慢且失败的角落,只读那几行,是从“200 条 Trace”缩到“这 6 条值得打开”的最快路径。 +这张图本身也是过滤器。点击一个点可以选中它,或者 **拖一个矩形框选一片点**,结果列表会收窄到这批选择;标题区切换成 *"N picked"* 计数,并提供 Reset。这是基于已加载结果的客户端过滤,不会发起新查询。直接框住慢且失败的角落,只看那几行,是从“200 条 Trace”缩到“这 6 条值得打开”的最快路径。 - + 图 1:先配置条件、执行查询,再在分布图上框选一片区域,把列表缩到真正值得打开的 Trace。</br> 每条结果行会显示 Trace 的 root endpoint、OK/ERR 标记、duration,以及按当前结果中最慢 Trace 归一化后的长度条。每一行 *代表什么* 取决于存储后端,Horizon 会自动检测:横幅会显示 *"Full traces are returned inline"*,表示后端返回的是带 spans 的完整 Trace,点击即可打开;或者显示 *"Each row is a trace segment — click one to fetch its full trace."*。你不需要配置这个,横幅只是告诉你当前看到的是什么。 @@ -48,12 +48,12 @@ Traces 标签页是一个位于 **Layer 内部** 的分布式 Trace 探索器: 点击一行后,Trace 会打开,并在同一组 spans 上提供三种视图切换:**Default**、**Tree** 和 **Statistics**。 -- **Default** 是 span 瀑布图:每个 span 一行缩进展示,带一个按服务着色的条形,条形在共享时间线上按 span 起始偏移和耗时定位;同时显示 span-kind glyph、组件图标(和拓扑地图共用同一套图标)、endpoint 或 peer 名称,以及 span 自身耗时。错误 span 会高亮,带 attached events 的 span 会有标记。关键点在于,瀑布图会用 parent references **把跨 segment 的 spans 串起来**,所以一个跨过五个服务的请求会渲染成一条连贯时间线,而不是五段互不相干的内容。 +- **Default** 是 span 瀑布图:每个 span 一行缩进展示,带一个按服务着色的条形,条形在共享时间线上按 span 起始偏移和耗时定位;同时显示 span-kind 标记、组件图标(和拓扑地图共用同一套图标)、endpoint 或 peer 名称,以及 span 自身耗时。错误 span 会高亮,带 attached events 的 span 会有标记。关键点在于,瀑布图会用 parent references **把跨 segment 的 spans 串起来**,所以一个跨过五个服务的请求会渲染成一条连贯时间线,而不是五段互不相干的内容。 - **Tree** 把同一批 spans 画成可缩放、可平移的节点图:root 在左,callees 向右流动。适合你更关心调用树形状,而不是精确时序的时候。 - **Statistics** 按 **name** 聚合 spans:一张可排序表,展示每个 operation 的 count、total / average / maximum duration。所以“这条 Trace 里到底哪个 span 名称累计耗时最多”只需要点一次排序。 - -图 2:瀑布图(Default),一条请求触达的所有服务被画成同一条连贯时间线。</br> + +图 2:瀑布图(Default),一条请求触达的所有服务会串成同一条连贯时间线。</br>  图 3:Tree 视图,把同一批 spans 画成调用树形状,可以缩放和平移。</br> @@ -65,13 +65,13 @@ Traces 标签页是一个位于 **Layer 内部** 的分布式 Trace 探索器: - **Cross-trace refs**:当一个 span 的 parent 位于 *另一条* Trace(异步跳转、稍后消费的消息)中时,这里会列出 parent 的 trace id、segment 和 span;trace id 是一个 **链接**,点击可以直接切换到那条 Trace。 - **Tags**、**Logs**(每个 span 的带时间戳日志项)和 **Attached Events**(带 start/end time 和 summary key/values 的命名事件)。 -详情标题区会显示 Trace 开始时间、总耗时、span 数量,以及触达了多少个不同服务;从这里可以复制 trace id 或可分享链接。打开一个带 `?traceId=...` 的分享 URL,会直接打开这条 Trace 的 overlay。这让 Trace 成为可以粘贴到 incident channel 里、让同事打开同一视图的对象。 +详情标题区会显示 Trace 开始时间、总耗时、span 数量,以及触达了多少个不同服务;从这里可以复制 trace id 或可分享链接。打开一个带 `?traceId=...` 的分享 URL,会直接打开这条 Trace 的 overlay。这样你可以把链接粘到 incident 频道里,让同事打开同一条 Trace、同一个视图。 ## Native 与 Zipkin 并列支持 并不是每个 Layer 的 Trace 都来自 SkyWalking 自己的 agent。Layer 模板带一个 `traces.source` 设置,可以是 `native`、`zipkin` 或 `both`,Horizon 据此路由。由 agent 接入的 Layer(比如 General Service)使用上面讲的 **native** 探索器;service-mesh 和 Kubernetes 风格的 Layer 中,spans 以 Zipkin/OpenTelemetry 数据进入,则使用 **Zipkin** 探索器;设置为 `both` 的 Layer 会得到 **两个标签页**,因为 native 和 Zipkin spans 的形状和条件确实不同。 -Zipkin 标签页通过 **OAP 的 Zipkin query API** 查询上游 Zipkin store。这是 OAP 向任何 Zipkin client 暴露的兼容接口,不是 GraphQL,也不是 TraceQL。Zipkin 按自己的服务集合组织数据(每个 span 上的 `serviceName`,可能和 SkyWalking 的服务列表不同),所以这个标签页有自己的服务控件,不绑定页面上的 service picker;它也带 Zipkin 原生条件,比如 *Remote service*、*Span name* 和 *Annotations* 查询(`error` 或 `key=value`)。两个存储路径独立失败:Zipkin 不可达时,native traces 不受影响,反之亦然。 +Zipkin 标签页通过 **OAP 的 Zipkin query API** 查询上游 Zipkin store。这是 OAP 向任何 Zipkin client 暴露的兼容接口,不是 GraphQL,也不是 TraceQL。Zipkin 按自己的服务集合组织数据(每个 span 上的 `serviceName`,可能和 SkyWalking 的服务列表不同),所以这个标签页有自己的服务选择器,不绑定页面上的 service picker;它也带 Zipkin 原生条件,比如 *Remote service*、*Span name* 和 *Annotations* 查询(`error` 或 `key=value`)。两个存储路径独立失败:Zipkin 不可达时,native traces 不受影响,反之亦然。  图 4:设置为 Zipkin 的 Layer 会得到自己的标签页和自己的服务集合,并通过 OAP 查询上游 Zipkin store。</br> @@ -83,10 +83,10 @@ Zipkin 标签页通过 **OAP 的 Zipkin query API** 查询上游 Zipkin store。 ## 从慢记录定位到对应 Trace -还有一种进入 Trace 的方式,不需要经过探索器。整个应用只挂载一个 Trace overlay,多个入口都能按 **trace id** 打开它:`?traceId=` 链接、cross-trace ref、日志行,以及[第二篇](/zh/2026-06-21-horizon-ui-dashboards-and-mqe/)里提到的 record 组件慢语句行上的 **jump-to-trace** 图标。因为它按 id 解析,而不是按 Layer 解析,所以即使 Trace 属于你当前看到的 *另一个* Layer 也能工作:Virtual Database、Cache 或 MQ 服务没有自己的 traces 标签页,但它的慢语句仍然能带你到发起方 Trace。跳转如果带 timestamp(日志行知道自己写入时间),查找会围绕那个时间放宽窗口,所以即便 Trace 已经进入冷存储,也不会悄悄找不到。 +还有一种进入 Trace 的方式,不需要经过探索器。Horizon 在应用级只挂载一个 Trace overlay,多个入口都能按 **trace id** 打开它:`?traceId=` 链接、cross-trace ref、日志行,以及[第二篇](/zh/2026-06-21-horizon-ui-dashboards-and-mqe/)里提到的 record 组件慢语句行上的 **jump-to-trace** 图标。因为它按 id 解析,而不是按 Layer 解析,所以即使 Trace 属于你当前看到的 *另一个* Layer 也能工作:Virtual Database、Cache 或 MQ 服务没有自己的 traces 标签页,但它的慢语句仍然能带你到发起方 Trace。跳转如果带 timestamp(日志行知道自己写入时间),查找会围绕那个时间放宽窗口,所以即便 Trace 已经进入冷存储,也不会悄悄找不到。 ## 后续阅读 Traces 标签页把一个请求完整展开;前面几篇的仪表盘和地图,是你最初发现异常的地方。字段参考,包括每个条件、native-vs-Zipkin 拆分和 span 详情面板,可以看 [Traces 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/operate/traces/)。 -下一篇:**Log Explorer**。同样的排查思路,应用到日志流而不是 spans。 +下一篇看日志探索器:从 Trace 切到相关日志流,沿着同一条排查路径继续定位问题。 diff --git a/content/zh/2026-06-23-horizon-ui-browser-errors-and-source-maps/index.md b/content/zh/2026-06-23-horizon-ui-browser-errors-and-source-maps/index.md index 3d876173634..c44dc5afa07 100644 --- a/content/zh/2026-06-23-horizon-ui-browser-errors-and-source-maps/index.md +++ b/content/zh/2026-06-23-horizon-ui-browser-errors-and-source-maps/index.md @@ -2,7 +2,7 @@ title: "认识 Horizon UI · 8/17:浏览器错误与 Source Map" date: 2026-06-23 author: 吴晟 -description: "Horizon UI 系列第八篇:浏览器端 Agent 上报的 JavaScript 错误流,以及如何借助 source map 把生产环境混淆栈逐帧还原到原始文件、行、列、符号和源码片段。" +description: "Horizon UI 系列第八篇:浏览器端 agent 上报的 JavaScript 错误流,以及如何借助 source map 把生产环境压缩后的 stack 还原到原始文件、行、列、符号和源码片段。" tags: - Logging - Engineering @@ -12,20 +12,20 @@ tags: 这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第八篇。[第七篇](/zh/2026-06-23-horizon-ui-log-explorer/)讲的是服务日志;这一篇讲 *用户* 遇到的错误,也就是浏览器端 agent 上报的 JavaScript 异常,以及把这些错误定位到源码的关键一步。 -生产环境 JavaScript stack 基本不可读。代码经过压缩和打包后发布,浏览器只会报告错误出现在 `app.min.js:1:98412`,也就是一段机器生成代码里的位置,几乎不给你任何线索。Horizon 要做的是借助正确的 **source map** 把这条 stack 还原回你的源码:原始文件、行、列、符号名,以及出错位置附近的代码片段,逐帧还原。 +生产环境 JavaScript stack 基本不可读。代码经过压缩和打包后发布,浏览器只会报告错误出现在 `app.min.js:1:98412`,也就是一段机器生成代码里的位置,几乎不给你任何线索。Horizon 要做的是找到正确的 **source map**,把 stack 里的每一帧映射回源码:原始文件、行、列、符号名,以及出错位置附近的代码片段。 ## 浏览器端错误流 -在 **BROWSER** Layer 上,**Browser Logs** 标签页(屏幕上的标签名,它专门表示 JavaScript 错误流)会列出 browser agent 上报的内容。BROWSER Layer 会把槽位重命名成自己的语义:services 变成 **Applications**,instances 变成 **Versions**,endpoints 变成 **Pages**。这个列表的阅读方式类似 [Log Explorer](/zh/2026-06-23-horizon-ui-log-explorer/):可点击的 **category** legend 带计数,density histogram 位于日志流之上。每一行都有时间、**category**、page、app version 和错误消息;如果带有 minified `line:col`,也会显示成标记。 +在 **BROWSER** Layer 上,**Browser Logs** 标签页(屏幕上的标签名,专指 JavaScript 错误流)会列出浏览器端 agent 上报的内容。BROWSER Layer 会把槽位重命名成自己的语义:services 变成 **Applications**,instances 变成 **Versions**,endpoints 变成 **Pages**。这个列表的阅读方式类似 [Log Explorer](/zh/2026-06-23-horizon-ui-log-explorer/):可点击的 **category** legend 带计数,density histogram 位于日志流之上。每一行都有时间、**category**、page、app version 和错误消息;如果带有压缩后的 `line:col`,也会显示成标记。 排查方式和 trace/log 标签页一致:它使用独立的 **Time range**(全局顶栏暂停),你可以按 **Version**、**Page** 或 **Category** 收窄,然后点击 **Run query**。没有后台轮询把视图不断往前推,也没有要学习的查询语言,只有结构化控件。点击一行后,它会在日志流里原地展开。 - -图 1:browser agent 上报的错误流:按 category 组织、带图表,并限定到某个 app 版本和页面。</br> + +图 1:浏览器端 agent 上报的错误流:按 category 组织、带图表,并限定到某个 app 版本和页面。</br> -那个 minified `line:col` 就是最典型的问题。它是真实位置,但位置在 *构建后* 的 bundle 里,不在你的源码里。后面的解析流程就是为了解决它。 +压缩后的 `line:col` 就是最典型的问题。它是真实位置,但位置在 *构建后* 的 bundle 里,不在你的源码里。后面的解析流程就是为了解决这个落差。 -## 从混淆后的 stack 定位到源码 +## 从压缩后的 stack 定位到源码 展开一个错误后,面板分成两侧:左边是浏览器原样报告的 **raw stack**,也就是那段很难读的生产栈;右边是解析结果区域。选择一个 **source map**,点击 **Resolve**,Horizon 会解析 stack,并通过这份 map 映射 **每一帧**: @@ -33,12 +33,12 @@ tags: - 原始 **symbol name**(如果 map 携带了); - 出错行附近几行 **原始源码**,命中行会高亮(如果 map 内嵌 `sourcesContent`)。 -map 覆盖不到的 frame 会明确标为 `unmapped`。所以一条顶层 frame 为 `app.min.js:1:45` 的 stack,可以还原成 `checkout.ts:2:20` 上的 `computeCartTotal`,并把 `checkout.ts` 附近几行显示出来。真正抛错的 `cart.items.reduce(...)` 就在面板里,不只还原第一帧,而是从上到下还原整条 stack。 +map 覆盖不到的 frame 会明确标为 `unmapped`。所以一条顶层 frame 为 `app.min.js:1:45` 的 stack,可以还原成 `checkout.ts:2:20` 上的 `computeCartTotal`,并把 `checkout.ts` 附近几行显示出来。真正抛错的 `cart.items.reduce(...)` 就在面板里,不只是还原第一帧,而是从上到下还原整条 stack。 这里有一些细节决定结果是否可信:浏览器 stack 的列号从 1 开始计数,source map 从 0 开始计数,所以解析器每次查找前都会做偏移;这条路径用真实 bundler 输出测试,而不是手写 fixture。 - -图 2:核心流程:把 minified stack 指向正确 map,然后逐帧还原到你自己的源码。</br> + +图 2:核心流程:把压缩后的 stack 匹配到正确 map,然后逐帧还原到你自己的源码。</br> ## 哪些错误可以用 source map 解析 @@ -46,10 +46,10 @@ map 覆盖不到的 frame 会明确标为 `unmapped`。所以一条顶层 frame ## 提供 source map:上传或挂载 -解析前必须让 map 可用。Horizon 提供两种方式,耐久性刻意不同: +解析前必须让 map 可用。Horizon 提供两种方式,持久化策略有意不同: - **Upload** 一个 `.map`,直接从标签页上传。它只保存在服务端 **内存** 里,没有后端存储,并且临时性是设计目标:它占用内存预算,在压力下按 least-recently-used 淘汰,**服务重启后丢失**;多实例部署时,它也只存在于接收上传的那一个实例。这个路径适合临时排查:拖一个 map 进来,解析,处理完离开。 -- **Mount** `.map` 文件到服务端 **source-map 目录**(容器镜像中是 `/app/sourcemaps`,可通过 `HORIZON_SOURCEMAPS_DIR` 指定)。这些文件在启动时按 Source Map v3 校验,按需从磁盘读取(所以不占内存预算),重启后仍然存在,会自动 reload,并且 **不能从 UI 删除**。这是生产路径:把构建产物里的 maps 放进镜像或挂载目录,它们就一直可用。 +- **Mount** `.map` 文件到服务端 **source-map 目录**(容器镜像中是 `/app/sourcemaps`,可通过 `HORIZON_SOURCEMAPS_DIR` 指定)。这些文件在启动时按 Source Map v3 校验,按需从磁盘读取(所以不占内存预算),重启后仍然存在,会自动重新加载,并且 **不能从 UI 删除**。这是生产路径:把构建产物里的 map 文件放进镜像或挂载目录,它们就一直可用。 管理器会显示每个 map 的来源(*uploaded · temporary* 还是 *mounted · durable*),以及当前内存预算使用量。预算配置,包括单文件上限和常驻上传总量上限,默认 64 MiB 和 512 MiB,位于 `horizon.yaml` 的 `sourceMaps` 块。 @@ -58,12 +58,12 @@ map 覆盖不到的 frame 会明确标为 `unmapped`。所以一条顶层 frame ## map 必须手动选择 -Horizon 刻意 **不猜测**。browser agent 会上报 app **version**,但不会上报精确 build fingerprint,所以没有安全方法自动把一个错误匹配到某份 map。用错 build 的 map 会给出非常自信、但完全错误的行号,比没有答案更糟。所以这里由你选择:挑选和该错误 build 对应的 map,并按版本给 map 清晰命名。(还有一个必须直说的注意点:source map 的 `sourcesContent` 会包含你的原始源码,所以无论上传还是挂载,都要把 map 当成敏感内容,只放在可信服务器上。) +Horizon 刻意 **不猜测**。浏览器端 agent 会上报 app **version**,但不会上报精确的构建指纹,所以没有安全方法自动把一个错误匹配到某份 map。用错构建的 map 会给出非常自信、但完全错误的行号,比没有答案更糟。所以这里由你选择:挑选和这次错误对应构建的 map,并按版本给 map 清晰命名。(还有一个必须直说的注意点:source map 的 `sourcesContent` 会包含你的原始源码,所以无论上传还是挂载,都要把 map 当成敏感内容,只放在可信服务器上。) -手动选择 map 这件事,也划清了 **权限** 边界。查看错误、列出 maps、**解析** stack 都是读操作,由 `browser-errors:read` 控制;**上传或删除** map 是写操作,由 `source-map:write` 控制。所以只读用户可以整天反混淆 stack,但没有权限改变已加载的 map 集合。读就是读,修改 map 存储才是写。 +手动选择 map 这件事,也划清了 **权限** 边界。查看错误、列出 maps、**解析** stack 都是读操作,由 `browser-errors:read` 控制;**上传或删除** map 是写操作,由 `source-map:write` 控制。所以只读用户可以反复解析 stack,但没有权限改变已加载的 map 集合。读就是读,修改 map 存储才是写。 ## 后续阅读 -字段参考,包括 categories、两种提供路径、预算,以及如何按 build 匹配 map,可以看 [Browser Logs & Source Maps 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/operate/browser-source-maps/)。 +字段参考,包括 categories、两种提供路径、预算,以及如何按构建版本匹配 map,可以看 [Browser Logs & Source Maps 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/operate/browser-source-maps/)。 -下一篇英文系列将进入 **Profiling**:五种 profiler(trace、async、eBPF、Go pprof、network)通过同一个火焰图呈现。 +英文原文的下一篇进入 **Profiling**:五种 profiler(trace、async、eBPF、Go pprof、network)共用一套火焰图视图。 diff --git a/content/zh/2026-06-23-horizon-ui-log-explorer/index.md b/content/zh/2026-06-23-horizon-ui-log-explorer/index.md index f7880c656fd..e615ebcf728 100644 --- a/content/zh/2026-06-23-horizon-ui-log-explorer/index.md +++ b/content/zh/2026-06-23-horizon-ui-log-explorer/index.md @@ -15,11 +15,11 @@ tags: - **Logs** 标签页查询 SkyWalking 已经 **采集并存储** 的日志:已索引、可过滤、可与 Trace 关联。 - **Pod Logs** 标签页按需 **实时 tail** Kubernetes pod 的容器日志。这类日志不走 SkyWalking 的日志存储:OAP 直接从 **Kubernetes API server** 读取它们(也就是 `kubectl logs` 那条路径),Horizon 只展示当前窗口,然后丢弃,不会持久化。 -某个 Layer 展示哪些标签页由模板决定:启用日志的 Layer(General、Mesh、Nginx、Envoy AI Gateway、mobile 和 mini-program Layer)会显示 **Logs** 标签页;只有感知 Kubernetes 的 Layer(Kubernetes Service、Mesh、Mesh data plane)会显示 **Pod Logs** 标签页。Browser JavaScript 错误又是另一类数据:它不是服务日志,而是浏览器端 agent 上报的客户端错误事件,有自己的分类,也有自己的 **source-map 反混淆**,可以把混淆后的 `app.min.js:1:...` frame 还原到原始 `file:line`。这是 Browser Layer 上的独立标签页,会在这个系列的另一篇文章里讲。 +某个 Layer 展示哪些标签页由模板决定:启用日志的 Layer(General、Mesh、Nginx、Envoy AI Gateway、mobile 和 mini-program Layer)会显示 **Logs** 标签页;只有感知 Kubernetes 的 Layer(Kubernetes Service、Mesh、Mesh data plane)会显示 **Pod Logs** 标签页。Browser JavaScript 错误又是另一类数据:它不是服务日志,而是浏览器端 agent 上报的客户端错误事件,有自己的分类,也可以借助 **source map** 把压缩后的 `app.min.js:1:...` frame 还原到原始 `file:line`。这是 Browser Layer 上的独立标签页,会在这个系列的另一篇文章里讲。 ## 查询已存储日志 -打开一个有 **Logs** 标签页的 Layer,先在顶部选择服务,最新日志流会按 newest-first 加载。和 Trace 探索器一样,这个标签页使用 **独立的时间范围**。当你在这里排查时,全局顶栏时间选择器会暂停,自动刷新不会把你正在看的窗口不断往前推。可以选择滚动预设(最近 15 分钟到 24 小时,默认 30 分钟),也可以选择自定义绝对窗口;查询按 **秒级精度** 执行,所以最新日志不会被分钟取整吞掉。 +打开一个有 **Logs** 标签页的 Layer,先在顶部选择服务,日志流会按新到旧加载。和 Trace 探索器一样,这个标签页使用 **独立的时间范围**。当你在这里排查时,全局顶栏时间选择器会暂停,自动刷新不会把你正在看的窗口不断往前推。可以选择滚动预设(最近 15 分钟到 24 小时,默认 30 分钟),也可以选择自定义绝对窗口;查询按 **秒级精度** 执行,所以最新日志不会被分钟取整吞掉。 条件栏用来收窄日志流,每个过滤条件都是可选的,多个条件按 AND 连接: @@ -33,18 +33,18 @@ tags: ## 用直方图和级别计数定位日志 -日志视图的目标不只是 *列出* 行,还要帮你看出分布和异常,所以日志流上方有两个定位信息。 +日志视图的目标不只是 *列出* 行,还要帮你看出分布和异常,所以日志流上方有两类辅助信息。 **Density histogram** 按时间展示日志量,每个柱按 legend 颜色 **堆叠 level**;hover 柱子可以看到该 bucket 的时间范围和每个 level 的计数。它基于当前页面上可见数据绘制,所以展示的是你正在看的日志分布。**Levels** 条则保留每个 level 在窗口内的累计计数。这个计数跨整个查询窗口采样,而不只是当前可见页,所以 error/warn/info 比例反映的是整个窗口。 -日志行会展示 timestamp、level(行颜色跟随 level)、service、存在 Trace 关联时的 **↗ trace** 链接、**`JSON` / `YAML` / `TEXT` 格式标记**,以及内容的一行预览。Horizon 按日志内容本身决定这个标记:OAP 会标注日志 body 是 JSON 还是 plain text,在此基础上 Horizon 还会嗅探 JSON 和 YAML 结构,所以即使一行没有被标注但内容是结构化的,也会得到正确处理。JSON 在预览里压平成一行,YAML 保留 key,plain text 会折叠空白。 +日志行会展示 timestamp、level(行颜色跟随 level)、service、存在 Trace 关联时的 **↗ trace** 链接、**`JSON` / `YAML` / `TEXT` 格式标记**,以及内容的一行预览。Horizon 按日志内容本身决定这个标记:OAP 会标注日志 body 是 JSON 还是 plain text,在此基础上 Horizon 还会识别 JSON 和 YAML 结构,所以即使一行没有被标注但内容是结构化的,也会得到正确处理。JSON 在预览里压平成一行,YAML 保留 key,plain text 会折叠空白。  图 1:某个服务的存储日志流:窗口上的 level histogram 和 level 计数,下面是每行日志,带 JSON / YAML / TEXT 标记并可跳到 Trace。</br> ## 查看单行日志详情 -点击一行后,完整日志内容会在弹层里打开:内容会按格式进行 **pretty-printing**,JSON 和 YAML 正常排版,plain text 则获得完整画布,而不是被挤在一条窄条里。面板还提供 **Copy** 按钮、service / instance / endpoint / trace 上下文,以及该行所有 tag 的表格。日志行与 Trace 关联时,**↗ trace** 按钮会在 overlay 中打开相关 [Trace 瀑布图](/zh/2026-06-22-horizon-ui-trace-explorer/),不离开日志流;它还会把这行日志的 timestamp 传过去,所以 Trace 即使已经进入更冷的存储 tier,也仍然能找到。按 Escape 或点击 backdrop 即可关闭。 +点击一行后,完整日志内容会在弹层里打开:内容会按格式排版,JSON 和 YAML 正常缩进,plain text 则展开成完整内容,而不是被挤在一条窄条里。面板还提供 **Copy** 按钮、service / instance / endpoint / trace 上下文,以及该行所有 tag 的表格。日志行与 Trace 关联时,**↗ trace** 按钮会在 overlay 中打开相关 [Trace 瀑布图](/zh/2026-06-22-horizon-ui-trace-explorer/),不离开日志流;它还会把这行日志的 timestamp 传过去,所以 Trace 即使已经进入更冷的存储 tier,也仍然能找到。按 Escape 或点击背景遮罩即可关闭。  图 2:完整查看一行日志:内容按格式排版,旁边展示上下文和所有 tags。</br> @@ -53,15 +53,15 @@ tags: **Pod Logs** 标签页回答另一个问题,它的数据源也完全不同:不是 SkyWalking 存储的日志,而是通过 OAP 从 **Kubernetes API server** 实时读取 pod 的容器输出,也就是 `kubectl logs -f` 读取的同一类内容。这里没有可翻页的存储历史;每次刷新拉取尾部窗口,展示出来,然后丢弃。 -启动 tail 只需要几个选择:选一个 **Pod**(固定的服务实例)、一个 **Container**(Horizon 会列出 pod 的 containers 并默认选第一个)、一个回看 **Window**(last 30s、1m、5m、15m 或 30m,决定每次轮询向前取多远),以及轮询 **Interval**(2s、5s、10s 或 30s,决定多久重新取一次)。按 **Start** 后,日志会流入只读查看器,保持最新行可见,并持续轮询直到你按 **Pause**。顶部状态条显示 container、行数、live indicator,以及上次更新距今多久。两行 **Include** / **Exclude** filter 用来收窄可见内容;每个标签都是一个 **整行正则表达式**(`.*error.*`),由 OAP 执行,所以它匹配整行而不是子串,并且可以叠加。 +开始 tail 只需要几个选择:选一个 **Pod**(固定的服务实例)、一个 **Container**(Horizon 会列出 pod 的容器并默认选第一个)、一个回看 **Window**(last 30s、1m、5m、15m 或 30m,决定每次轮询向前取多远),以及轮询 **Interval**(2s、5s、10s 或 30s,决定多久重新取一次)。按 **Start** 后,日志会流入只读查看器,保持最新行可见,并持续轮询直到你按 **Pause**。顶部状态条显示 container、行数、live 状态,以及上次更新距今多久。两行 **Include** / **Exclude** 过滤器用来收窄可见内容;每个标签都是一个 **整行正则表达式**(`.*error.*`),由 OAP 执行,所以它匹配整行而不是子串,并且可以叠加。 -使用前需要知道一点:按需读取 pod logs 在 OAP 上 **默认关闭**,因为容器输出可能包含 secret。功能关闭时,或者你选择的 pod 已经滚动或缩容消失时,OAP 会返回一个 *原因*,Horizon 会把它显示成横幅,而不是给你一个空面板。这样你能区分“需要打开这个功能”和“那个 pod 已经不存在”。 +使用前需要知道一点:按需读取 pod logs 在 OAP 上 **默认关闭**,因为容器输出可能包含敏感信息。功能关闭时,或者你选择的 pod 已经滚动或缩容消失时,OAP 会返回一个 *原因*,Horizon 会把它显示成横幅,而不是给你一个空面板。这样你能区分“需要打开这个功能”和“那个 pod 已经不存在”。  图 3:一个 pod 容器的实时 tail:窗口化、按间隔轮询、可用正则过滤、永不持久化。</br> ## 后续阅读 -两个标签页,包括存储查询、tag 和 container autocomplete、live tail,都由同一个 `logs:read` 权限控制。所以授予“可以读日志”就是一个开关。字段参考,包括每个条件、histogram、Pod Logs 的窗口和过滤器,可以看 [Logs 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/operate/logs/)。 +两个标签页,包括存储查询、tag/container 自动补全和 live tail,都由同一个 `logs:read` 权限控制。所以授予“可以读日志”就是一个开关。字段参考,包括每个条件、histogram、Pod Logs 的窗口和过滤器,可以看 [Logs 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/operate/logs/)。 -下一篇:**Browser & RUM monitoring**。browser agent 自己的错误流,以及如何用 source map 反混淆 minified stack。 +下一篇看 Browser/RUM:浏览器端错误如何上报,又如何用 source map 把压缩后的 stack 还原到源码位置。
