This is an automated email from the ASF dual-hosted git repository. wu-sheng pushed a commit to branch blog/horizon-ui-cn-translations in repository https://gitbox.apache.org/repos/asf/skywalking-website.git
commit a58ec325fd64633f0293e9975072880c9ae15911 Author: Wu Sheng <[email protected]> AuthorDate: Mon Jun 29 00:46:14 2026 +0800 docs(blog): add Chinese Horizon UI translations --- .../index.md | 91 ++++++++++++ .../index.md | 85 ++++++++++++ .../index.md | 74 ++++++++++ .../index.md | 152 +++++++++++++++++++++ .../index.md | 91 ++++++++++++ .../2026-06-22-horizon-ui-trace-explorer/index.md | 92 +++++++++++++ .../index.md | 69 ++++++++++ .../zh/2026-06-23-horizon-ui-log-explorer/index.md | 67 +++++++++ 8 files changed, 721 insertions(+) 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 new file mode 100644 index 00000000000..c5937f6a30d --- /dev/null +++ b/content/zh/2026-06-21-horizon-ui-dashboards-and-mqe/index.md @@ -0,0 +1,91 @@ +--- +title: "认识 Horizon UI · 2/17:会适配实体的仪表盘" +date: 2026-06-21 +author: 吴晟 +description: "Horizon UI 系列第二篇:它的仪表盘如何由 MQE 表达式驱动,如何隐藏不适用于当前实体的组件并在服务端跳过查询,以及如何把 1 显示成 OK、把 4.51e4 显示成 45.1k。" +tags: + - Metrics + - Cloud Native +--- + +*本文翻译自英文原文:[Meet Horizon UI · 2/17: Dashboards That Adapt — MQE, Smart Widgets, and Numbers Humans Can Read](/blog/2026-06-21-horizon-ui-dashboards-and-mqe/),发布日期沿用原文日期。* + +这是 [Apache SkyWalking Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第二篇。第一篇介绍了控制台和按 Layer 组织的导航;这一篇讲你每天最常停留的界面:**仪表盘**。 + +Horizon 里的每个仪表盘,本质上都是同一台机器:一个组件网格,每个组件都是一条由 BFF 解析并发往 OAP 的 **MQE 表达式**。它值得单独写一篇,不只是因为能展示数字,而是因为它围绕数字做了很多实际工作:不适用于当前实体的组件会被隐藏,相关查询也完全不会发出;编码值和原始字节数会被渲染成人能读懂的形式;页面上所有图表都绑定到同一个游标;你还可以从一条慢 SQL 记录直接跳到产生它的 Trace。下面逐项看。 + +## 每个组件都是一条 MQE 表达式 + +Layer 仪表盘是一个紧凑的 **12 列网格**:行高 120px,空隙会自动回填,避免墙一样的组件块出现洞;宽度低于约 1100px 时会折叠成单列。每个格子是五类组件之一,而组件类型取决于 MQE 表达式返回数据的 *形状*: + +- **`card`**:表达式收敛为单个标量,比如 `latest(...)`、`avg(...)`、`service_sla/100`。显示一个大数字。 +- **`line`**:时间序列;每个表达式一条线,混合单位时可以使用双 Y 轴,比如左侧吞吐、右侧时延。 +- **`top`**:来自 `top_n(endpoint_cpm, 20, des)` 的排行列表,带一个小标签切换器,可以在 Traffic / Slow / Successful Rate 之间切换排序。 +- **`record`**:记录型输出,比如慢数据库语句或慢缓存命令:文本行加数值。 +- **`table`**:带标签的 `latest(...)` 指标,每组 label 一个表格行,比如每个服务的 pod phase、node condition、deployment replicas。 + +你不需要手工选择图表类型;写好 MQE,合适的组件会自己渲染。而且每个层级都使用同一套网格系统。Layer 模板里的 `dashboards.<scope>` map 会为 **service**、**instance** 和 **endpoint** 页面携带不同的组件集合,所以向下钻取时,整个仪表盘会换成正确的 scope。(这些都运行在[第一篇](/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 scope 上,根据选中实例的属性开关,比如 `language eq JAVA`,或者某个属性是否存在。 + +因为条件在 **服务端** 计算,非 JVM 实例不只是把 JVM 格子藏起来;BFF 根本不会把这些查询发给 OAP。用同一个 Instance 仪表盘打开一个 JVM 服务和一个非 JVM 服务,你看到的是一个模板在适配当前实体,而不是两套手工维护的页面。 + + +图 1:JVM 实例上会渲染 JVM 组件,因为它们的 `visibleWhen` 条件成立。</br> + + +图 2:同一个仪表盘打开在 Go 实例上,JVM 组件不存在,查询也没有执行。一个模板,按实体适配。</br> + +## 数字要让人看得懂 + +原始指标不一定是可读指标。Horizon 的组件会处理三类过去常让运维人员在脑子里换算的值: + +- **`enum`**:用 value→label map 把编码型 gauge 变成文字。`1/0` 成功指标会显示成 **`OK`** / **`Failed`**,而不是裸数字。label 可以按 locale 翻译。 +- **`duration`**:以秒为单位的指标会显示成人能理解的时间差,比如 **`5m 20s ago`**;在坐标轴上会压缩成 `5m` / `2h`。 +- **SI suffixes**:图表坐标轴和 tooltip 上的大数会显示成 **`45.1k`**、**`1.34M`**、**`2.5G`**,而不是 `4.51e4`。坐标轴刻度和 hover 值使用同一套写法。 + + +图 3:高密度字节数和计数序列使用紧凑 SI 后缀,坐标轴和 tooltip 保持一致。</br> + + +图 4:`enum` 和 `duration` 格式在 BanyanDB 生命周期卡片上的效果:`OK` 代替 `1`,"5m 20s ago" 代替秒数。</br> + +## 把整个网格当成同一条时间线读 + +页面上所有 `line` 图共享 **同一个 hover 游标**。指向吞吐图上的第 32 分钟,时延图、错误率图和每个 sparkline 格子上的第 32 分钟也会一起亮起。这个契约在图表 wrapper 层强制执行,任何组件都不能退出,所以页面读起来是一组围绕同一时刻协同的视图,而不是十几个互不相关的图。多序列 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。语句文本本身也支持 **点击复制**。 + + +图 6:从慢语句跳到执行它的 Trace。按 trace id 解析,所以即使虚拟 Layer 自己没有 traces 标签页也能工作。</br> + +## 固定并对比多个实体 + +有时只看一个实体不够。Horizon 允许你 **锁定多个服务、实例或 endpoint,甚至来自不同服务的实体,并在当前页面内直接比较**。可以从选择器或 instance/endpoint 列表固定实体;当前正在查看的实体始终属于对比组,会标记为 `CURRENT`,并继续驱动顶部 header。每个固定实体都有自己的颜色。之后每个组件都会内联对比:line 组件为每个实体叠加一条序列,card 为每个实体显示一行,`top` 和 `record` 组件增加按实体切换的标签页,table 增加 Entity 列。持久的对比栏会保持这个实体组,不受底层列表分页或当前查看实体变化影响;每个实体单独发起请求,所以一个实体慢,不会把其他实体拖成空白。 + + +图 7:锁定实体,甚至跨服务锁定,所有折线组件都会按颜色叠加;对比栏保持实体组,CURRENT 实体仍然驱动 header。</br> + +## 时间选择器驱动整个仪表盘 + +顶部时间范围会驱动页面上的所有内容:header KPI 条、组件主体,以及 BanyanDB 分层 hot/warm/cold 存储里的 **Cold** pill 完整流程。过去落地页和拓扑路由固定在最近 60 分钟,所以选择 "12 days ago" 后仍然会悄悄展示近期数字;现在时间选择器在所有地方都生效。上游控制变化时,每个依赖它的格子会明确重置并显示 "Reading data..." 提示,而不是在 spinner 下面留着旧值。 + +## 下一步去哪里 + +需要强调的是,这是一套系统。同样的五类组件、同样的 MQE、同样的条件开关,会渲染每个 Layer 的仪表盘:上面的 JVM 面板、BanyanDB 生命周期卡片、mesh 服务上的百分位时延,以及专门为 Envoy AI Gateway(token throughput、time-to-first-token)或 GenAI virtual layer(per-model estimated cost)设计的面板。不同 Layer 之间变化的是 MQE,不是机制。 + +上面讲的是 *阅读* 体验。每个组件的 MQE、`visibleWhen` 条件、格式,以及每个 scope 的网格,都可以从 **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 在这里用图表展示的同一批数据,画成一张可以走进去看的地图。 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 new file mode 100644 index 00000000000..5c89f253fe9 --- /dev/null +++ b/content/zh/2026-06-21-horizon-ui-deployment-and-banyandb/index.md @@ -0,0 +1,85 @@ +--- +title: "认识 Horizon UI · 4/17:Deployment 标签页与 BanyanDB 自观测" +date: 2026-06-21 +author: 吴晟 +description: "Horizon UI 系列第四篇:Deployment 标签页把拓扑地图转向单个集群服务内部,展示它自己的实例;BanyanDB 则按真实的角色和层级建模,让 SkyWalking 终于能像观测其他系统一样观测自己的数据库。" +tags: + - Cloud Native + - Storage +--- + +*本文翻译自英文原文:[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 按它真实的集群形态建模出来。 + +## Deployment 标签页:一个服务自身实例的地图 + +服务地图回答“谁调用这个服务”。新的按 Layer 配置的 **Deployment** 标签页回答另一个问题:“*这个* 服务是怎么部署的,它自己的实例之间又是怎么通信的?”选择一个服务后,这个标签页会把它的实例画成节点,并画出实例之间的调用关系。你已经在服务地图里见过的平移/缩放画布、健康 ring 节点、边流动动画和每条调用的指标侧栏都会复用,只是 scope 收缩到单个服务内部。 + +它不是一团扁平节点云,关键在三点: + +- **实例渲染成六边形,并组合成 pod。** 一个 pod 的 **main** container 是完整六边形,**sibling** containers 会作为更小的六边形贴在它边上。所以主进程和 sidecar 会读成一个单元,跨 pod 的 sidecar 链接也会连到它真正所属的小六边形上。 +- **Pod 按你选择的规则聚到带标签的 box 里。** 规则可以是单个实例属性(role)、多个属性组合(比如 `node_role` + `node_type`),也可以是名称正则。这样一组混合角色节点会读成每个角色一个 box,而不是一团云。 +- **布局是分层的。** 每个 cluster box 会按调用深度摆放 pod:source 在左,被调用对象在右,所以 upstream→downstream 链条可以从左到右读;拖动任意 pod 后,它所属的 box 会重新流式布局,保持内容都被包住。 + +边按 **(source-role → target-role)** pair 建 key,所以每类链接展示自己的指标,而不是共享一组扁平指标。主指标会直接印在边上,完整指标则进入 **Flows** 子标签页,每种 role-pair 对应一张对齐表。它默认关闭,和服务地图一样,完全从 **Layer dashboards admin → Deployment scope** 配置。 + +## 像观测其他系统一样观测 BanyanDB + +这套机制是为一个明确场景准备的。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 标签页,会直接画出这个数据库集群自身。 + + +图 1:SkyWalking 观测自己的数据库:BanyanDB 集群按角色和 tier 绘制,并展示 pod 之间的 liaison→data 与 lifecycle→data 边。</br> + +### Cluster、Container、Group + +每个 scope 都有专门设计的仪表盘: + +- **Cluster** 仪表盘是整个数据库的战情室:write / query / error-rate KPI、CPU / memory / disk capacity、吞吐和错误趋势,以及 **Containers by Role** 表。 +- **Container** 仪表盘会 **适配选中容器的 role**。每个容器都有 CPU / memory / Go runtime 资源面板;**liaison** 会增加 ingestion、query、gRPC errors、tier-2 publish pipeline 和 write-queue depth;**data** 节点会增加 storage totals、merge / compaction、inverted index、subscribe queue 和 retention;**lifecycle** sidecar 会显示 migration cycles 和 last-run time / status。角色专属面板按容器 role 属性开关,所以你只会看到当前节点适用的内容。 +- **Group** 仪表盘按 **data model** 拆分:measure、stream、trace、property。因为一个 BanyanDB group 只存储一个 catalog,只有匹配该模型的面板会渲染:`measure` group 显示 write-rate / query-latency / merge 面板,`property` group 显示 index-write / term-search / series 面板,以此类推。 + +<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-02-cluster-dashboard.webp" alt="BanyanDB Cluster 仪表盘:write/query/error KPI、CPU/memory/disk capacity,以及 Containers by Role 表。" 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;">图 2:Cluster scope,一眼看完整个数据库,并按角色列出所有容器。</figcaption> +</figure> + +打开 *同一个* 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;"> +<figcaption style="flex:1 1 240px;color:#5f6b7a;font-size:.92em;line-height:1.6;">图 3:data/liaison Container,在共享资源面板之上展示 ingestion、query、storage 和 compaction 面板。</figcaption> +</figure> + +<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-lifecycle-dashboard.webp" alt="同一个 Container 仪表盘打开在 lifecycle sidecar 上,只展示 migration-cycle 和 last-run 面板。" 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;">图 4:同一个仪表盘打开在 lifecycle 节点上,只显示 migration 面板。同一模板,按 role 开关。</figcaption> +</figure> + +**Group** scope 则给每个 storage catalog 一个自己的页面: + +<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 scope,一次查看一个 storage catalog,它的面板按 group 的 data model 开关。</figcaption> +</figure> + +### 认识 role pair 的边,以及 Flows 表 + +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> + +这里有两个前提。边和角色专属面板假设你运行的是一个真实的 **集群化** BanyanDB;单进程 standalone 实例只会显示共享资源和 Go runtime 面板,其余面板会在集群角色开始上报后亮起来。尤其是 container-to-container 边,还需要 OAP 构建暴露 `SERVICE_INSTANCE_RELATION` scope;在那之前,Deployment 标签页仍然会画完整 inventory,只是 pod 之间没有边。 + +## 配置出来的,不是写死的 + +上面这些不是一张手工写死的 "BanyanDB screen"。聚类规则、每个 role 的节点指标、role-pair 边指标,都在 Layer template 中作为一个自包含块存在,可以从 **Layer dashboards admin → Deployment scope** 编辑,并随模板 export/import 一起携带。它和其他 Layer 背后的配置驱动模型一样,后续文章会完整展开。 + +下一篇:**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 new file mode 100644 index 00000000000..f82fdfac4d8 --- /dev/null +++ b/content/zh/2026-06-21-horizon-ui-topology-and-dependency/index.md @@ -0,0 +1,74 @@ +--- +title: "认识 Horizon UI · 3/17:拓扑与服务依赖" +date: 2026-06-21 +author: 吴晟 +description: "Horizon UI 系列第三篇:一套由模板驱动、可为每个 Layer 重绘的拓扑引擎,降噪过滤器,从调用下钻到实例,endpoint 依赖图,以及跨 Layer 的 Smartscape 叠加视图。" +tags: + - Cloud Native + - Tracing +--- + +*本文翻译自英文原文:[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 中是什么样子。 + +SkyWalking 拓扑背后的调用数据是一份,但 Horizon 从中画出的 *视图* 不止一种。它有每个 Layer 自己的服务地图,可以从单条调用下钻到背后的实例,可以看 endpoint 级依赖图,也可以用跨 Layer 叠加视图把一个服务的多张面孔连起来。它们共用同一套引擎,只是回答的问题不同。(Deployment 标签页,也就是单个集群服务 *内部* 实例的地图,以及 WebGL 3D 地图,都足够大,会在接下来的文章里单独讲。) + +## 一套拓扑引擎,按 Layer 重绘 + +打开任意 Layer 的 **Topology** 标签页,你会看到一张从左到右排列的层次化服务地图:`User` 存在时位于最左侧,每个服务按调用深度落在不同列里;同一列内部则保留图遍历到它们时的顺序,所以主调用链可以自上而下读。每个服务都是一个 **六边形节点**,节点上展示的所有东西都来自这个 Layer 的配置,没有写死逻辑: + +- 六边形 **边框** 承载节点的 **ring** 指标,用类似 SLA 的健康色带展示(绿色 → 红色); +- 组件 **图标** 放在六边形内部,和 Trace 瀑布图使用同一套图标,所以 PostgreSQL 节点像 PostgreSQL,Kafka 节点像 Kafka; +- 节点头部数字,也就是 **center** 指标,带单位显示在六边形 **上方**; +- 服务 **名称** 显示在节点 **下方**,**secondary** 指标(默认是时延)显示在名称下面; +- 每条 **边** 都带调用吞吐的 **RPM chip**,使用服务端指标,缺失时回退到客户端指标。 + +这里需要强调一点:上面这些都只是 **General Layer 的内置默认配置**。节点的 center / ring / secondary 指标,以及边指标,都位于 **Layer dashboards admin → Topology scope**,本质上是带单位和角色的 MQE 表达式。所以你可以把任意槽位指向另一条指标,同一套引擎就会为另一个 Layer 画出不同地图,或者按你的方式重画当前 Layer。(这些选择会像其他模板内容一样,跟着 Layer template 的 export/import 走。) + + +图 1:一套由模板驱动的拓扑引擎:带健康色带的六边形节点、带 RPM chip 的边、真实组件图标;图上的每个指标都按 Layer 配置。</br> + +## 削掉噪声 + +真实拓扑通常很吵。一张密集地图里会混入 OAP 没法完整识别的推测节点,比如裸露的 `rcmd:80`、未接入探针的地址。Horizon 的 **Filter** 控件可以关掉这些噪声,同时保留真实依赖。它会自动推导一个 facet:**按 Layer 分组**,展示方式和侧边栏一致。每一行都有 Layer 自己的图标和本地化名称,比如 *Virtual Database*、*Java Agent*,还会有一个 **Others** 桶,用来收纳 OAP 无法分类的节点,以及一个独立的 **User** 开关。取消勾选 *Others* 后,未接入探针的杂点和悬空边会消失,而数据库、缓存和队列(各自的 `VIRTUAL_*` 行)仍然留在图上。过滤在客户端执行,行会在每次刷新时重新推导,所以不会陈旧。 + + +图 2:一键降噪,去掉无法解析的 "Others" 节点,同时保留真实依赖。</br> + +当某个 Layer 的服务落在 OAP service group 里,地图上的 service-focus 选择器也会按这些 group 分组。点击 group header 可以 **批量选中或清空该组里的所有服务**,所以你能一次聚焦一张繁忙地图中某个团队负责的那一片。 + + +图 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;每次改动其中一个,另一个都会重新推导。 + + +图 4:从一条聚合调用下钻到背后的实例到实例流量。</br> + +## 按 endpoint 走完整请求链 + +服务拓扑回答“哪些服务调用了这个服务”。**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 peers,节点上会贴一个小的 **chevron-stack chip**。 + + +图 6:选中节点上的 chevron-stack chip。它在选择时惰性探测,只在服务存在跨 Layer peers 时显示。</br> + +点击这个 chip,拓扑会在 **Smartscape** 叠加视图下变暗:焦点节点在原位高亮重绘,它的 peers 按 OAP 的 Layer 顺序纵向展开,请求近端 Layer 在上,基础设施近端 Layer 在下。之后两步点击就能在对应 Layer 中打开任意 peer,并完成预选。(叠加视图打开时自动刷新会暂停,避免内容在你眼前移动。) + + +图 7:一个服务在所有上报 Layer 中的样子,以一键叠加视图展示跨 Layer hierarchy。</br> + +## 下一步去哪里 + +这些地图上的每个指标、阈值和边权重,都位于 Layer template 的 `topology` 块里。换句话说,你会用和仪表盘一样的配置驱动方式调整它们,这也是后续文章的主题。字段参考可以看 [layer-template](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/customization/layer-templates/) 里的 topology 文档。 + +下一篇:**Deployment 标签页和 BanyanDB 自观测**。同样的地图技术会转向 *内部*,展示一个集群服务自身实例是如何部署、如何相互通信的。 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 new file mode 100644 index 00000000000..9b1ae491c5e --- /dev/null +++ b/content/zh/2026-06-21-skywalking-horizon-ui-introduction/index.md @@ -0,0 +1,152 @@ +--- +title: "认识 Horizon UI · 1/17:SkyWalking 新一代可观测性控制台" +date: 2026-06-21 +author: 吴晟 +description: "介绍 Apache SkyWalking Horizon UI:基于同一个 OAP 后端重新设计的新一代 Web UI,从一条能实时反映系统全貌的侧边栏开始,让观测、运维、治理和定制都回到同一个控制台里。" +tags: + - Release + - Cloud Native +--- + +*本文翻译自英文原文:[Meet Horizon UI · 1/17: SkyWalking's New Observability Console](/blog/2026-06-21-skywalking-horizon-ui-introduction/),发布日期沿用原文日期。* + +Apache SkyWalking Horizon UI 是 SkyWalking 的新一代 Web 控制台。它仍然连接你已经在运行的 OAP 后端:同样的 GraphQL 查询协议,同样的 admin REST 接口,同样的 MQE 语言,同样的 `Layer` 概念。所以你可以直接把 Horizon 指向一个正在运行的 OAP,然后登录使用,不需要改后端。变化发生在这个契约之前的整个用户界面。 + +这是这个系列的第一篇。后续文章会依次介绍仪表盘和指标查询语言、拓扑视图、整个部署的 WebGL 3D 地图、链路和日志探索器、性能剖析、运维界面、访问控制,以及把这些能力串起来的配置化定制。本篇先交代背景:Horizon 是什么,整个 UI 围绕哪一个核心想法构建,以及今天如何把它接到你的 OAP 前面。 + +Horizon 围绕四个动词展开。你先 **observe**:拓扑、链路、日志、五类性能剖析、只读告警,以及每个 Layer 的仪表盘。然后你可以 **operate** 你正在观测的对象,**govern** 谁可以操作它们,并在不写 UI 代码的情况下 **customize** 整个控制台。Observe、operate、govern、customize,就是这个系列的主线。我们从每次会话都会看到的地方开始:侧边栏。 + +## 侧边栏就是你的系统全貌 + +打开 Horizon,左侧边栏不是手工写死的菜单,而是 OAP 当前上报内容的实时映射。Horizon 会向 OAP 查询有哪些 Layer、哪些 Layer 里有服务,然后只渲染这些内容,并每 60 秒刷新一次。某个 Layer 开始上报,它就出现;安静下来,它就消失。菜单不会和现实漂移,因为菜单本身就是轮询出来的现实。 + + +图 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。 + +点击任意 Layer,Horizon 会打开它的第一个可用标签页。所有 Layer 都沿用同一条脊柱: + +```text +service → instance → endpoint → topology → trace → logs → profiling +``` + +槽位名称会跟随 Layer 的语义变化。图 2 中的 General Service Layer 会把 endpoint 槽位命名为 **API**,增加一个 **API dependency** 视图,并把 **Profiling** 展开成它实际拥有的引擎:Trace、eBPF、pprof(Go)和 Async。某个 Layer 不支持的标签页会在模板里直接关掉,所以你不会落到空页面上。选中一个服务后,右侧画布就是这个服务的仪表盘:上方是一排 KPI(RPM、Apdex、错误率,每个都有自己的 sparkline),下方是只针对这个实体的组件网格。 + + +图 2:展开一个 Layer 后进入完整流程,左侧是标签脊柱,右侧是选中服务的仪表盘。</br> + +## 不再给你一个空白页 + +一个跟随实时数据变化的控制台,必须能处理“没有数据”的时刻:全新安装、配置不完整的部署,或者刚刚重启的 OAP。Horizon 把这些都当成一等状态,而不是死胡同。 + +打开 `/` 时,Horizon 会逐级落到一个真实目的地:第一个可用的公共 overview 仪表盘;如果没有,就进入第一个有服务的 Layer;只有两者都不存在时,才展示空落地页。真的进入空页面时,它会用明确语言告诉你问题在哪里:**"No data is flowing yet"** 表示还没有任何内容上报,**"No dashboard configured yet"** 表示已经有服务,但没有配置 overview。它会指向运维团队,而不是把你丢在一个空网格上。只要有服务开始上报,或者运维人员发布了仪表盘,下一次 60 秒刷新就会把空页面替换成真实页面。 + + +图 3:空落地页说明真实原因。这里是服务已经上报,但没有配置 overview,而不是给出一个空仪表盘。</br> + +OAP 短暂不可达时,Horizon 也遵循同样思路。如果后端短时间连不上,Horizon 会保留最后一次已知的侧边栏结构,并显示 "OAP unreachable" 横幅,服务计数标记为未知,直到恢复为止。短暂故障不会看起来像配置突然消失。 + +当数据正常流入时,落地页就是图 1 里看到的战情室:跨 Layer 概览、按类型拆分的服务卡片(General services、Virtual databases、caches、MQs、GenAI)、实时拓扑和活跃告警栏。每个 Layer 自己的落地页会按你选择的列真正计算 top-N 服务,不再先截取任意前 25 个再排序;页面还会告诉你 "top N of M",所以截断不会悄悄发生。 + +长 Layer 名称和深命名空间也很常见,所以 shell 会主动让出空间:拖动分隔线可以调整侧边栏宽度,双击重置;也可以折叠成一条窄图标栏,把水平空间尽量交给画布。宽度会按浏览器记住。 + + +图 4:拖动分隔线加宽侧边栏,长名称不再被截断,宽度会按浏览器记住。</br> + + +图 5:需要最大画布空间时,可以把侧边栏折叠成窄图标栏。</br> + +## 新增一层,后面的能力才成立 + +过去,SkyWalking Web UI 是浏览器直接访问 OAP。Horizon 在中间加了一小层基础设施:**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 启动时会探测一次 OAP 的 GraphQL schema,某个能力不存在时就优雅降级;Horizon 能用同一个构建支持两代 OAP,靠的就是这个机制。它还会每分钟缓存一次服务目录,让整个 UI 对系统全貌持有同一份视图。运维、安全和定制相关的文章会分别展开这些内容;这里先记住一点:现在这里有一个服务端,它承担了实实在在的工作。 + +## 用 3D 看完整部署 + +有一个界面值得先提前看,因为它最能表达“后退一步,一次看清全局”的想法:**3D Infrastructure Map**。每个 Layer 的服务都会变成立方体,堆叠到按请求流向排列的层级上,实时流量、告警和调用关系都画在它们之间。拖动即可旋转: + +{{< map3d poster="/images/home/horizon-3d-map.png" badge="交互演示 · 示例数据" >}} + +后续有一篇专门拆解 3D 地图:层级、告警信标、让健康对象变成幽影而只突出告警对象的 "Beacon mode",以及配置它的结构化编辑器。现在先把它当成 Horizon 目标的一个缩影:整个部署放在一个视图里,而且是活的。 + +## 这个系列会讲什么 + +本篇之后还有十五篇,每篇都会独立介绍 Horizon 的一个角落。可以按任意顺序阅读,每篇也都会链接回这里。它们分成四条主线。 + +**看见你的数据** + +1. **Dashboards & MQE**:只查询当前实体相关数据的组件、人类可读的数值格式、同步十字线和多实体对比。 +2. **Topology & service dependency**:一套可为每个 Layer 重绘的拓扑引擎、降噪过滤器和多跳 API dependency 图。 +3. **The Deployment tab & BanyanDB self-observability**:深入单个集群服务内部的视图,以及 SkyWalking 终于像观测其他对象一样观测自己的数据库。 +4. **The 3D Infrastructure Map**:完整展开上面这个 3D 视图。 +5. **Trace explorer**:在时延散点图上框选慢 Trace,然后用三种方式阅读同一条 Trace。 +6. **Log explorer**:类似 Loki 的日志流,带 facets、top patterns 和结构化 payload。 +7. **Browser & RUM monitoring**:前端错误日志,以及把混淆后的栈恢复到原始源码行。 +8. **Five profilers, one flame graph**:trace、async-profiler、eBPF、Go pprof 和 network profiling,统一到同一套工作方式里。 + +**运维它** + +9. **Alarms & incident triage**:以 incident 为中心的活跃告警,并带上触发它的图表。 +10. **Runtime rules, live debugging & inspect**:可热加载规则,以及用实时样本逐步调试 OAL(traces)、MAL(metrics)和 LAL(logs)的 Live Debugger。 +11. **Platform & cluster introspection**:在 UI 里查看 OAP 集群健康、最终解析后的配置和数据保留策略。 + +**治理和保护它** + +12. **Access control & security**:服务端强制执行的 RBAC、LDAP/AD、审计轨迹和 break-glass,让 UI 可以进入企业部署。 + +**把它变成你的控制台,并完成接入** + +13. **Customization: config-driven layer templates**:从 draft 到 publish,不写 UI 代码也能增加一个全新的被监控 Layer。 +14. **Localization**:八种语言的仪表盘,并且可以在实时预览里点击组件完成翻译。 +15. **Getting started & migration**:安装、OAP 版本矩阵,以及如何用 Horizon 替换现有 UI。 + +## 今天就接到你的 OAP 上试试 + +Horizon 可以运行在你已有的 OAP 之上。在今天的 **OAP 10.x** 上,绝大多数功能已经可用:所有仪表盘、拓扑、Trace(原生和 **Zipkin**)、日志、告警,以及五类性能剖析,都通过 OAP 的 query host(`:12800`)渲染。Horizon 的访问控制、审计和主题运行在 BFF 里,不依赖 OAP 版本。需要等待 **OAP 11.0** 的是 *operate* 层:runtime-rule(DSL)管理、Live Debugger、Metrics Inspect、告警规则编辑器、Cluster Status 管理面板,以及把模板编辑发布回 OAP。这些依赖 admin host(`:17128`),即将随 OAP 11.0 发布。Horizon 会按 admin module 是否存在来探测能力,10.x 不能提供的页面会直接隐藏;完整观测控制台今天就能跑在 10.x 上,迁移到 11.0 后运维工具会自动亮起来。 + +把 Horizon 指向已有集群即可启动,不需要改后端。仍然是你的部署已经在使用的那个 OAP: + +```sh +docker run -d --name horizon \ + -p 8081:8081 \ + -v "$PWD/horizon.yaml:/app/horizon.yaml:ro" \ + -v horizon-state:/data \ + ghcr.io/apache/skywalking-horizon-ui:<version> +``` + +最小化的 `horizon.yaml` 只需要说明 OAP 在哪里,以及一个可登录的本地用户: + +```yaml +oap: + queryUrl: http://<oap-host>:12800 + adminUrl: http://<oap-host>:17128 +auth: + backend: local + local: + users: + - username: admin + passwordHash: "$argon2id$v=19$..." # generated, never plaintext + roles: [admin] +``` + +打开 `http://<host>:8081/`,登录后第一站是 **Cluster Status**,确认 Horizon 和 OAP 能正常通信。之后侧边栏就会填入你的系统全貌。 + +完整安装路径,包括 binary tarball、Kubernetes、LDAP、TLS 和生产检查清单,请看 [Horizon UI 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/readme/)。左侧菜单里覆盖了安装、兼容性、访问控制、定制、组件和运维。 + +## 其他值得注意的点 + +- **可以直接接入现有 OAP。** Horizon 是一次从零开始的重写,但保留了所有后端契约:同样的 GraphQL 查询协议、admin REST 接口、MQE 语言和 `Layer` 概念。所以你可以把它指向一个正在运行的集群,不改后端。今天的 **OAP 10.x** 已经能运行完整观测控制台(仪表盘、拓扑、包括 **Zipkin** 在内的 Trace、日志、告警、性能剖析),以及 Horizon BFF 侧的访问控制、审计和主题。只有 *operate* 工具链需要等待 OAP 的 **admin host**(`:17128`),也就是随 **OAP 11.0,即将发布** 的 runtime rules、Live Debugger、Inspect、Cluster Status admin pane 和模板编辑发布。 +- **暗色优先,高密度。** 12 列网格面向 incident 扫描设计,首屏承载更多信号,减少不必要留白。 +- **基于现代技术栈。** 前端是 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 都可以。 + +下一篇:仪表盘,以及为什么一个组件可以在服务端判断自己不适用于当前实体,并且连查询都不发。 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 new file mode 100644 index 00000000000..6ef2be5c2ab --- /dev/null +++ b/content/zh/2026-06-22-horizon-ui-3d-infrastructure-map/index.md @@ -0,0 +1,91 @@ +--- +title: "认识 Horizon UI · 5/17:3D 基础设施地图" +date: 2026-06-22 +author: 吴晟 +description: "Horizon UI 系列第五篇:用一个 WebGL 视图查看整个部署,把每个 Layer 的服务渲染成立方体并堆叠到不同 tier 上,同时显示实时流量、告警信标和调用关系。" +tags: + - Cloud Native + - Engineering +--- + +*本文翻译自英文原文:[Meet Horizon UI · 5/17: The 3D Infrastructure Map](/blog/2026-06-22-horizon-ui-3d-infrastructure-map/),发布日期沿用原文日期。* + +这是 [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 示例数据上的真实地图。拖动旋转,滚轮缩放,点击立方体: + +{{< map3d poster="/images/home/horizon-3d-map.png" badge="交互演示 · 示例数据" >}} + +## 一个场景看完整系统 + +3D 地图是 `/3d/map` 下的独立全屏视图,从顶栏里的 **3D Infra** pill 打开。它刻意拿掉控制台其余部分:没有侧边栏,没有顶栏,没有全局时间选择器。整个 viewport 都交给场景。SkyWalking 标识放在左下角,右上角的 `×` 返回 Horizon。里面所有数据都来自 Horizon 其他页面访问的同一个 OAP:服务清单、每个 Layer 的拓扑、每个服务的流量,以及活跃告警。 + + +图 1:一个场景看完整部署:服务是立方体,Layer 是带颜色的 zone,角色按 tier 堆叠。</br> + +## 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),并查看当前可见服务数量。 + +## 怎么读地图:立方体、zone、流量 + +每个 **立方体就是一个服务**。立方体会在 tier 上按所属 Layer 聚成一个 **zone**:半透明矩形使用该 Layer 的品牌色,并盖上项目 logo(Istio 的帆、Kubernetes 舵轮、数据库圆柱、队列图标),所以任何相机角度下都能辨认出 zone。带拓扑的 Layer(General、Service Mesh、Kubernetes Service、Cilium)会按 **调用依赖** 摆放立方体:上游 caller 在一侧,下游服务在另一侧,对应 2D 服务地图的 3D 版本。没有拓扑的 Layer 则把立方体排成整齐网格。 + +立方体下方的小 **traffic pill** 显示该服务的实时主吞吐:应用和 mesh 服务是 requests per minute,数据服务是 queries 或 operations per second,并保留各自单位。只有足够近、可读时 pill 才出现;缩远后会淡出,保持场景干净;选中的立方体总会显示它的数字。 + +## 告警,以及 incident 用的 Beacon mode + +当一个服务有 **当前正在触发的告警** 时(Horizon 轮询最近 20 分钟,并只统计 service scope 下仍在触发的告警),它的立方体会 **红色脉冲**。这就是一个信标,即使隔着整个场景也能看到,而告警 feed 会独立刷新。 + +在繁忙地图里,几百个立方体中的一个红点仍然可能难找,所以有 **Beacon mode**。从工具栏打开后,所有 *健康* 立方体都会变成深色 wireframe 幽影,只留下正在告警的服务发光。部署形状仍然可读,但真正出问题的服务才有颜色。它把鸟瞰图一键变成 incident triage 界面。 + + +图 2:Beacon mode 会把健康对象变暗成幽影,所以你只会看到正在触发的服务。</br> + +## 对象之间的线 + +地图不只画节点,也画调用图: + +- **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 看到的服务。它们表示 *身份*,不是流量,所以默认隐藏;选中立方体后只显示它自己的 relatives,并沿着 tier 一层层爬上去。这就是[第三篇](/zh/2026-06-21-horizon-ui-topology-and-dependency/)里的 Smartscape,用它本来就该拥有的维度画出来。 + + +图 3:选中立方体后,身份链接沿 tier 爬升:同一个服务,被 agent、mesh 和 Kubernetes 分别看到。</br> + +## 怎么移动 + +拖动旋转,滚轮缩放,**方向键** 或 **WASD** 平移视角(按住 **Shift** 步长更大);左上角工具栏也为触控板提供同样动作的按钮。有一条刻意设计的规则值得知道:**3D 场景里的点击永远不会移动相机**,只负责选择。点击一个立方体,它会高亮,旁边出现详情卡片(服务名称、tier、Layer,以及会在新标签页打开该服务 Layer 仪表盘的 **Open dashboard** 按钮),它的 hierarchy links 也会亮起。移动相机的交互面是 *侧边面板* 和 *工具栏*;点击 Layer 行,相机会滑到它的 zone。把“选择”和“移动相机”分开,点击小立方体才会可靠,不会刚点中它就让它从光标下滑走。 + +## 它如何构建场景 + +完整部署数据太大,不适合一次请求拉完,所以地图分阶段加载,底部细长的 **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。正是这类细节,让几百个服务也能在浏览器标签页里平滑渲染。 + +## 配置出来的,不是写死的 + +上面这些不是一张写死的 "3D screen"。地图展示什么,由管理员在 `/admin/3d-map` 的 **结构化表单编辑器** 里编辑:tier、Layer、颜色和指标都通过表单控制,而不是直接写 JSON。你可以在里面: + +- **用一个全局正则过滤 Layer**:匹配排除的内容会完全从地图上消失。 +- **安排 tier**:重命名、从上到下重排,并把每个 Layer 固定到某个 tier 上,同时指定 failover tier,避免内容静默丢失。 +- **对 Layer 分组**:把多个相关 Layer,比如 SkyWalking 自观测组件,聚成一个带标签的 block,每个成员仍然保留自己的颜色。 +- **为每个 Layer 配色并选择流量指标**:配置 MQE 表达式、label 和单位;默认值会从该 Layer 的仪表盘模板里初始化,所以大多数 Layer 一开始就能显示合理数字。 + +Horizon 自带一份默认配置,所以地图开箱就有用。你的修改会以本地 draft 保存,直到点击 **Check diff & push** 发布到 OAP。它使用和仪表盘相同的 draft → preview → publish 模型,也支持同样的 Export/Import,用于备份或在部署之间迁移配置。地图本身是只读 **observe** 界面,可以直接运行在当前 OAP 上;发布塑造它的配置,则属于后续文章会完整介绍的配置化定制能力。 + + +图 4:地图是配置,不是代码。tier、颜色和每个 Layer 的流量指标都以表单方式编辑,然后发布到 OAP。</br> + +## 下一步去哪里 + +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**。从整个部署的鸟瞰图,回到单个请求,并用三种方式画出来。 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 new file mode 100644 index 00000000000..096457649f8 --- /dev/null +++ b/content/zh/2026-06-22-horizon-ui-trace-explorer/index.md @@ -0,0 +1,92 @@ +--- +title: "认识 Horizon UI · 6/17:Trace Explorer" +date: 2026-06-22 +author: 吴晟 +description: "Horizon UI 系列第六篇:按 Layer 使用的分布式 Trace 探索器,支持分阶段条件、可框选的时延分布图、三种阅读同一条 Trace 的方式,以及与原生 Trace 并列的 Zipkin 标签页。" +tags: + - Tracing + - Cloud Native +--- + +*本文翻译自英文原文:[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** 标签页。 + +## 为排查设计,而不是为 tailing 设计 + +Traces 标签页是一个位于 **Layer 内部** 的分布式 Trace 探索器:选择服务,设置条件,然后阅读单条 Trace 的 span 时间线。它有意和控制台其他部分不一样,因为 Trace 是排查数据,不是实时 feed。 + +它 **拥有自己的时间范围和条件**。它不跟随全局顶栏时间选择器,也不会自动刷新。你先把要找的条件摆好,再按 **Run query**;按之前不会取任何数据。第一次运行前,列表只显示 *"Pick your conditions, then click Run query."*。当你在追二十分钟前的一条坏 Trace 时,最不需要的就是页面每几秒自己往前滑,所以它不会这么做。 + +## 条件表单,不是查询语言 + +整个过滤界面都是结构化表单控件:select、数字范围、tag chips。条件会暂存在工具栏里,只在点击 **Run query** 后生效: + +- **Instance** 和 **Endpoint**:在服务内部收窄范围,endpoint 下拉框只列出这个服务自己的 endpoints。 +- **Status**:`ALL` / `SUCCESS` / `ERROR`。 +- **Order**:Newest(按开始时间)或 Slowest(按耗时)。 +- **Limit**:拉取多少行,默认 30;BFF 会在服务端限制 page size,避免客户端向 OAP 请求过量数据。 +- **Time range**:滚动预设(最近 15 分钟到 24 小时)或自定义绝对窗口,按 **秒级精度** 计算,所以刚结束的 Trace 不会因为分钟取整而掉出窗口。 +- **Trace ID**:粘贴一个 id 直接查。 +- **Duration range**:毫秒级 min-max。 +- **Tag**:自由输入 span tags,格式为 `key=value`(比如 `http.status_code=500`),按 Enter 添加为可删除 chip,多个 tag 按 AND 连接;key 和 value 都从后端获得 typeahead。 + +它不是查询语言。Horizon 里没有 TraceQL 输入框。上面的结构化条件就是完整界面。(TraceQL 是另一条路径:SkyWalking 后端可以通过 TraceQL 向 Grafana 提供 Trace,这件事在[另一篇文章](/zh/2026-04-08-traceql/)里讲。Horizon 的探索器是表单,不是 DSL。) + +## 可以框选的分布图 + +结果返回后,工具栏下会出现一张 **Distribution** 图:每个点代表一条 Trace,X 轴是开始时间,高度是 duration,越慢的 Trace 越高。点按 **status** 着色:错误为红色,成功为强调色。所以左上角、右上角那类高处红点,就是你要找的“又慢又失败”的区域。 + +这张图本身也是过滤器。点击一个点可以选中它,或者 **拖一个矩形框选一片点**,结果列表会收窄到这批选择;header 切换成 *"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."*。你不需要配置这个,横幅只是告诉你当前看到的是什么。 + +## 三种方式读同一条 Trace + +点击一行后,Trace 会打开,并在同一组 spans 上提供三种视图切换:**Default**、**Tree** 和 **Statistics**。 + +- **Default** 是 span 瀑布图:每个 span 一行缩进展示,带一个按服务着色的条形,条形在共享时间线上按 span 起始偏移和耗时定位;同时显示 span-kind glyph、组件图标(和拓扑地图共用同一套图标)、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> + + +图 3:Tree 视图,把同一批 spans 画成调用树形状,可以缩放和平移。</br> + +## 进入一个 span 内部 + +点击任意 span,旁边会打开详情面板。**Meta** 展示核心信息:service、instance、endpoint、kind(entry / exit / local / producer / consumer)、component、peer、layer、start time、duration 和 error 标记。适用时,下面还会出现: + +- **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 的命名事件)。 + +详情 header 会显示 Trace 开始时间、总耗时、span 数量,以及触达了多少个不同服务;从这里可以复制 trace id 或可分享链接。打开一个带 `?traceId=...` 的分享 URL,会直接落到该 Trace 的 overlay 上。这让 Trace 成为可以粘贴到 incident channel 里、让同事打开同一视图的对象。 + +## 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 不受影响,反之亦然。 + + +图 4:设置为 Zipkin 的 Layer 会得到自己的标签页和自己的服务宇宙,并通过 OAP 查询上游 Zipkin store。</br> + +打开一个 Zipkin span 后,详情会保留 Zipkin 的形状:`CLIENT` / `SERVER` kind、本地和远端 endpoint,以及原始 Zipkin/OpenTelemetry tags(`istio.*`、`http.status_code`、sidecar `node_id`)都会按 Zipkin 记录的方式展示,不翻译成 SkyWalking span 模型。 + + +图 5:Zipkin span 保留自己的字段:kind、endpoints,以及原始 `istio.*` / HTTP tags。</br> + +## 从慢记录跳到背后的 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 已经进入冷存储,也不会悄悄找不到。 + +## 下一步去哪里 + +Traces 标签页把一个请求完整展开;前面几篇的仪表盘和地图,是你最初发现异常的地方。字段参考,包括每个条件、native-vs-Zipkin 拆分和 span 详情面板,可以看 [Traces 文档](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/operate/traces/)。 + +下一篇:**Log Explorer**。同样的排查思路,应用到日志流而不是 spans。 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 new file mode 100644 index 00000000000..f9f38a2f0f6 --- /dev/null +++ b/content/zh/2026-06-23-horizon-ui-browser-errors-and-source-maps/index.md @@ -0,0 +1,69 @@ +--- +title: "认识 Horizon UI · 8/17:浏览器错误与 Source Maps" +date: 2026-06-23 +author: 吴晟 +description: "Horizon UI 系列第八篇:browser agent 上报的 JavaScript 错误流,以及让这些错误真正可用的能力:逐帧把生产环境混淆栈还原到原始文件、行、列、符号和源码片段。" +tags: + - Logging + - Engineering +--- + +*本文翻译自英文原文:[Meet Horizon UI · 8/17: Browser Errors & Source Maps](/blog/2026-06-23-horizon-ui-browser-errors-and-source-maps/),发布日期沿用原文日期。* + +这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第八篇。[第七篇](/zh/2026-06-23-horizon-ui-log-explorer/)讲的是服务日志;这一篇讲 *用户* 遇到的错误,也就是 browser agent 上报的 JavaScript 异常,以及把这些错误从噪声变成可处理问题的关键能力。 + +生产环境 JavaScript stack 基本不可读。代码经过压缩和打包后发布,浏览器只会报告错误出现在 `app.min.js:1:98412`,也就是一段机器生成代码里的位置,几乎不给你任何线索。这个功能的目的,是把这条 stack 沿着正确的 **source map** 走回你的源码:原始文件、行、列、符号名,以及出错位置附近的代码片段,逐帧还原。 + +## 浏览器错误流 + +在 **BROWSER** Layer 上,**Browser Logs** 标签页(屏幕上的标签名,它专门表示 JavaScript 错误流)会列出 browser agent 上报的内容。BROWSER Layer 会把槽位重命名成自己的语义:services 变成 **Applications**,instances 变成 **Versions**,endpoints 变成 **Pages**。这个 feed 的阅读方式类似 [Log Explorer](/zh/2026-06-23-horizon-ui-log-explorer/):可点击的 **category** legend 带计数,density histogram 位于日志流之上。每一行都有时间、**category**、page、app version 和错误消息;如果带有 minified `line:col`,也会显示成 chip。 + +排查方式和 trace/log 标签页一致:它拥有自己的 **Time range**(全局顶栏暂停),你可以按 **Version**、**Page** 或 **Category** 收窄,然后点击 **Run query**。没有后台轮询把视图从你脚下挪走,也没有要学习的查询语言,只有结构化控件。点击一行后,它会在日志流里原地展开。 + + +图 1:browser agent 的错误流:按 category 组织、带图表,并限定到某个 app 版本和页面。</br> + +那个 minified `line:col` 就是问题的缩影。它是真实位置,但位置在 *构建后* 的 bundle 里,不在你的源码里。后面的能力就是为了解决它。 + +## 从 minified stack 回到源码 + +展开一个错误后,面板分成两侧:左边是浏览器原样报告的 **raw stack**,也就是那段乱码;右边是解析结果区域。选择一个 **source map**,点击 **Resolve**,Horizon 会解析 stack,并通过这份 map 映射 **每一帧**: + +- 每帧原始 **`file:line:column`**; +- 原始 **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。 + +这里有一些细节决定结果可信还是悄悄错掉:浏览器 stack 的列号从 1 开始计数,source map 从 0 开始计数,所以解析器每次查找前都会做偏移;这条路径用真实 bundler 输出测试,而不是手写 fixture。 + + +图 2:核心能力:把 minified stack 指向正确 map,然后逐帧读回你自己的源码。</br> + +## 哪些错误带可解析 stack + +不是每类错误都有东西可翻译。**`JS`**、**`PROMISE`** 和 **`VUE`** 是真实 JavaScript 错误,它们的 stack 指向 bundle,可以解析。**`AJAX`** 和 **`RESOURCE`** 是网络和加载失败;它们的“stack”是 HTTP status 或失败 URL,不是代码,所以 source map 没有东西可映射(Horizon 不会阻止它们,只是那里没有 JavaScript 可以走回去)。没有 source map 的代码、`eval` 或 inline scripts 里的 frame,也会保持 `unmapped`。(`JS` 也是唯一由浏览器上报顶层 `line:col` 的 category;其他 category 的位置在 stack 字符串内部,由解析器提取。) + +## 怎么提供 map:上传,或者挂载 + +解析前必须让 map 可用。Horizon 提供两种方式,耐久性刻意不同: + +- **Upload** 一个 `.map`,直接从标签页上传。它只保存在服务端 **内存** 里,没有后端存储,并且临时性是设计目标:它占用内存预算,在压力下按 least-recently-used 淘汰,**服务重启后丢失**;多实例部署时,它也只存在于接收上传的那一个实例。这个路径适合临时排查:拖一个 map 进来,解析,处理完离开。 +- **Mount** `.map` 文件到服务端 **source-map 目录**(容器镜像中是 `/app/sourcemaps`,可通过 `HORIZON_SOURCEMAPS_DIR` 指定)。这些文件在启动时按 Source Map v3 校验,按需从磁盘读取(所以不占内存预算),重启后仍然存在,会自动 reload,并且 **不能从 UI 删除**。这是生产路径:把构建产物里的 maps 放进镜像或挂载目录,它们就一直可用。 + +管理器会显示每个 map 的来源(*uploaded · temporary* 还是 *mounted · durable*),以及当前内存预算使用量。预算配置,包括单文件上限和常驻上传总量上限,默认 64 MiB 和 512 MiB,位于 `horizon.yaml` 的 `sourceMaps` 块。 + + +图 3:两种提供 map 的方式:upload 用于快速排查,mount 用于稳定生产集。</br> + +## 你需要主动选择 map + +Horizon 刻意 **不猜测**。browser agent 会上报 app **version**,但不会上报精确 build fingerprint,所以没有安全方法自动把一个错误匹配到某份 map。用错 build 的 map 会给出非常自信、但完全错误的行号,比没有答案更糟。所以这里由你选择:挑选和该错误 build 对应的 map,并按版本给 map 清晰命名。(还有一个必须直说的注意点:source map 的 `sourcesContent` 会包含你的原始源码,所以无论上传还是挂载,都要把 map 当成敏感内容,只放在可信服务器上。) + +这种“有意手动”的选择,也划清了 **权限** 边界。查看错误、列出 maps、**解析** stack 都是读操作,由 `browser-errors:read` 控制;**上传或删除** map 是写操作,由 `source-map:write` 控制。所以只读 viewer 可以整天反混淆 stack,但没有权限改变已加载的 map 集合。读就是读,修改 map store 才是写。 + +## 下一步去哪里 + +字段参考,包括 categories、两种提供路径、预算,以及如何按 build 匹配 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)通过同一个火焰图呈现。 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 new file mode 100644 index 00000000000..4db118e40a4 --- /dev/null +++ b/content/zh/2026-06-23-horizon-ui-log-explorer/index.md @@ -0,0 +1,67 @@ +--- +title: "认识 Horizon UI · 7/17:Log Explorer" +date: 2026-06-23 +author: 吴晟 +description: "Horizon UI 系列第七篇:两种日志界面,一种是已采集、已索引、可与 Trace 关联的存储日志流,带 level 直方图;另一种是按需实时 tail Kubernetes pod 的容器日志。" +tags: + - Logging + - Cloud Native +--- + +*本文翻译自英文原文:[Meet Horizon UI · 7/17: The Log Explorer](/blog/2026-06-23-horizon-ui-log-explorer/),发布日期沿用原文日期。* + +这是 [Meet Horizon UI](/zh/2026-06-21-skywalking-horizon-ui-introduction/) 系列的第七篇。[第六篇](/zh/2026-06-22-horizon-ui-trace-explorer/)讲的是一个请求的 spans;这一篇讲它周围的日志行。Horizon 通过 **两个不同标签页** 展示日志,因为它们回答的是两个不同问题:*“这个服务过去半小时打了什么日志?”* 以及 *“这个 pod 现在正在向 stdout 打什么?”* + +- **Logs** 标签页查询 SkyWalking 已经 **采集并存储** 的日志:已索引、可过滤、可与 Trace 关联。 +- **Pod Logs** 标签页按需 **实时 tail** Kubernetes pod 的容器日志。这些不是存储日志:OAP 直接从 **Kubernetes API server** 读取它们(也就是 `kubectl logs` 那条路径),Horizon 展示窗口,然后丢弃。不会持久化,也不会进入 SkyWalking 日志存储。 + +某个 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 错误又是另一类数据:它不是服务日志,而是 browser agent 上报的客户端错误事件,有自己的分类,也有自己的 **source-map de-obfuscation**,可以把混淆后的 `app.min.js:1:...` frame 还原到原始 `file:line`。这是 Browser Layer 上的独立标签页,会在这个系列的另一篇文章里讲。 + +## 存储日志流 + +打开一个有 **Logs** 标签页的 Layer,先在 header 里选择服务,最新日志流会按 newest-first 加载。和 Trace 探索器一样,这个标签页 **拥有自己的时间范围**。当你在这里排查时,全局顶栏时间选择器会暂停,自动刷新不会把窗口从你脚下移走。可以选择滚动预设(最近 15 分钟到 24 小时,默认 30 分钟),也可以选择自定义绝对窗口;查询按 **秒级精度** 执行,所以最新日志不会被分钟取整吞掉。 + +条件栏用来收窄日志流,每个过滤条件都是可选的,多个条件按 AND 连接: + +- **Instance**:限制到某个服务实例。在 sidecar Layer 上标签显示为 **Sidecar**。 +- **Endpoint**:输入搜索服务 endpoints,点击固定,按 **×** 清除。 +- **Trace ID**:只显示和某条 Trace 关联的日志行。从 Trace 跳转过来时,也会预填这个字段并直接限定日志流。 +- **Tags**:单个 `key=value` 字段,带 autocomplete;输入 key 可看建议,输入 `=` 后切换到已知 value,按 Enter 提交。提交后的 tags 以可删除 chip 形式保留。 +- **Level**:日志流上方的 **Levels** 条也可以当过滤器用。点击 `error`、`warn`、`info` 或 `debug` 只保留该级别,再点一次清除。 + +这里没有日志查询语言,不需要学习 LogQL。上面的条件就是完整界面,并且 **编辑时会立即刷新日志流**;**Run query** 只是显式告诉系统“我改完了,现在刷新”,同时回到第一页。 + +## 怎么读日志流 + +日志视图的目标不是 *列出* 行,而是帮你找到形状,所以日志流上方有两个定位信息。 + +**Density histogram** 按时间画出日志数量,每个柱按 legend 颜色 **堆叠 level**;hover 柱子可以看到该 bucket 的时间范围和每个 level 的计数。它基于当前页面上可见数据绘制,所以展示的是你正在看的内容形状。**Levels** 条则保留每个 level 在窗口内的运行计数。这个计数跨整个查询窗口采样,而不只是当前可见页,所以 error/warn/info 比例反映的是整个窗口。 + +每行日志会显示 timestamp、level(行颜色跟随 level)、service、存在 Trace 关联时的 **↗ trace** 链接、**`JSON` / `YAML` / `TEXT` 格式 chip**,以及内容的一行预览。Horizon 按 payload 实际内容决定这个 chip:OAP 会标注 body 是 JSON 还是 plain text,在此基础上 Horizon 还会嗅探 JSON 和 YAML 结构,所以即使一行没有被标注但内容是结构化的,也会得到正确处理。JSON 在预览里压平成一行,YAML 保留 key,plain text 会折叠空白。 + + +图 1:某个服务的存储日志流:窗口上的 level histogram 和 level 计数,下面是每行日志,带 JSON / YAML / TEXT 标记并可跳到 Trace。</br> + +## 进入单行日志 + +点击一行后,完整 payload 会在 popout 里打开:完整内容会按 **格式感知 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 即可关闭。 + + +图 2:完整查看一行日志:payload 按格式排版,旁边展示上下文和所有 tags。</br> + +## Pod Logs:tail 当前正在输出的内容 + +**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** 后,窗口会流入只读 viewer,保持最新行可见,并持续轮询直到你按 **Pause**。header 条显示 container、行数、live indicator,以及上次更新距今多久。两行 **Include** / **Exclude** filter 用来收窄可见内容;每个 chip 都是一个 **整行正则表达式**(`.*error.*`),由 OAP 执行,所以它匹配整行而不是子串,并且可以叠加。 + +使用前需要知道一点:按需读取 pod logs 在 OAP 上 **默认关闭**,因为容器输出可能包含 secret。功能关闭时,或者你选择的 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/)。 + +下一篇:**Browser & RUM monitoring**。browser agent 自己的错误流,以及如何用 source map 反混淆 minified stack。
