This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-website.git
The following commit(s) were added to refs/heads/master by this push:
new 6ef355f0801 Blog: Meet Horizon UI 8/16 — Browser Errors & Source Maps
(#867)
6ef355f0801 is described below
commit 6ef355f080159a0a7e3edefb9ca401f70ab31c98
Author: 吴晟 Wu Sheng <[email protected]>
AuthorDate: Tue Jun 23 11:03:56 2026 +0800
Blog: Meet Horizon UI 8/16 — Browser Errors & Source Maps (#867)
---
.../index.md | 67 +++++++++++++++++++++
.../horizon-0.7.0/p08-browser-01-stream.webp | Bin 0 -> 105054 bytes
.../horizon-0.7.0/p08-browser-02-resolve.webp | Bin 0 -> 97252 bytes
.../p08-browser-03-sourcemap-manager.webp | Bin 0 -> 49552 bytes
4 files changed, 67 insertions(+)
diff --git
a/content/blog/2026-06-23-horizon-ui-browser-errors-and-source-maps/index.md
b/content/blog/2026-06-23-horizon-ui-browser-errors-and-source-maps/index.md
new file mode 100644
index 00000000000..865614e6943
--- /dev/null
+++ b/content/blog/2026-06-23-horizon-ui-browser-errors-and-source-maps/index.md
@@ -0,0 +1,67 @@
+---
+title: "Meet Horizon UI · 8/16: Browser Errors & Source Maps"
+date: 2026-06-23
+author: Sheng Wu
+description: "Part 8 of the Meet Horizon UI series: the browser agent's
JavaScript-error feed, and the capability that makes it useful — resolving a
minified production stack back to your original file, line, column, symbol and
source, frame by frame."
+tags:
+ - Logging
+ - Engineering
+---
+
+This is the eighth post in the [Meet Horizon
UI](/blog/2026-06-21-skywalking-horizon-ui-introduction/) series. [Part
7](/blog/2026-06-23-horizon-ui-log-explorer/) was your services' logs; this one
is your *users'* errors — the JavaScript exceptions the browser agent reports —
and the one capability that turns them from noise into something you can act on.
+
+A production JavaScript stack is unreadable. Your code shipped minified and
bundled, so the browser reports an error at `app.min.js:1:98412` — a position
into machine-generated soup that tells you nothing. The point of this feature
is to walk that stack back to *your* source: the original file, line, column,
symbol name, and a snippet of the code around it — frame by frame — by pointing
the error at the right **source map**.
+
+## The browser-error feed
+
+On the **BROWSER** layer, the **Browser Logs** tab (the on-screen label — it's
specifically the JavaScript-error feed) lists what your browser agent reports.
The BROWSER layer renames its slots to match its world — services become
**Applications**, instances **Versions**, endpoints **Pages** — and the feed
reads like the [Log Explorer](/blog/2026-06-23-horizon-ui-log-explorer/): a
clickable **category** legend with counts and a density histogram over a stream
of rows. Each row carries th [...]
+
+You scope it with the same triage instincts as the trace and log tabs: it owns
its own **Time range** (the global topbar is paused), and you narrow by
**Version**, **Page**, or **Category** and hit **Run query** — there's no
background polling to shift the view under you, and no query language to learn,
just structured controls. Click a row and it expands inline, right there in the
stream.
+
+
+Figure 1: The browser agent's error feed — categorized, charted, and scoped to
one app's version and pages.</br>
+
+That minified `line:col` is the whole problem in miniature. It's a real
position — but into your *built* bundle, not your source. Which is where the
rest of this post comes in.
+
+## From a minified stack back to your source
+
+Expand an error and the panel splits in two: on the left, the **raw stack**
exactly as the browser reported it (the gibberish); on the right, where you
resolve it. Pick a **source map** from the dropdown and click **Resolve**, and
Horizon parses the stack and maps **every frame** through that map:
+
+- each frame's original **`file:line:column`**,
+- the original **symbol name** (when the map carries it), and
+- a few lines of the **original source** around the offending line, with the
hit line highlighted (when the map embeds `sourcesContent`).
+
+A frame the map doesn't cover is shown honestly as `unmapped`. So a stack
whose top frame read `app.min.js:1:45` resolves to `computeCartTotal` at
`checkout.ts:2:20`, with the lines of `checkout.ts` around it — the
`cart.items.reduce(...)` that actually threw — sitting right there, the whole
stack top to bottom, not just the first frame.
+
+It's careful about the details that make this either trustworthy or quietly
wrong: browser stacks count columns from 1 while source maps count from 0, so
the resolver shifts before each lookup — and that path is tested against real
bundler output, not a hand-made fixture.
+
+
+Figure 2: The hero — point a minified stack at the right map and read it back
in your own source, frame by frame.</br>
+
+## Which errors carry a stack to resolve
+
+Not every category has something to translate. **`JS`**, **`PROMISE`**, and
**`VUE`** are real JavaScript errors whose stack points into your bundle —
these resolve. **`AJAX`** and **`RESOURCE`** are network and load failures;
their "stack" is an HTTP status or a failed URL, not code, so there's simply
nothing for a source map to map (Horizon doesn't block them — there's just no
JavaScript there to walk back). Frames from code with no source map, or from
`eval`/inline scripts, stay `unma [...]
+
+## Getting maps in: upload, or mount
+
+A map has to be available before you can resolve against it, and there are two
ways to provide one — deliberately different in durability:
+
+- **Upload** a `.map` straight from the tab. It's held in the server's
**memory only** — there's no backend storage — and it's temporary by design: it
counts against a memory budget, is evicted least-recently-used under pressure,
is **lost when the server restarts**, and (in a multi-instance deployment)
lives only on the instance that received it. This is the fast path for ad-hoc
triage: drag a map in, resolve, move on.
+- **Mount** `.map` files into the server's **source-map directory**
(`/app/sourcemaps` in the container image, via `HORIZON_SOURCEMAPS_DIR`). These
are validated as Source Map v3 at boot, read from disk on demand (so they never
sit in the memory budget), survive restarts, reload on their own, and **can't
be deleted from the UI**. This is the durable, production path — bake your
builds' maps into the image and they're always there.
+
+The manager shows each map's origin (an *uploaded · temporary* map vs a
*mounted · durable* one) and the live memory usage against the budget; budgets
(a per-file cap and a total resident-upload cap, 64 MiB and 512 MiB by default)
live in a `sourceMaps` block in `horizon.yaml`.
+
+
+Figure 3: Two ways to provide a map — upload for a quick triage, mount for the
durable, production set.</br>
+
+## You pick the map — on purpose
+
+One thing Horizon deliberately does *not* do is guess. The browser agent
reports an app **version** but no exact build fingerprint, so there's no safe
way to auto-match an error to a map — and applying a map from the *wrong* build
gives you confidently wrong line numbers, which is worse than no answer. So the
choice is yours: pick the map that matches the error's build, and keep your
maps labelled by version. (One caution worth stating plainly: a source map's
`sourcesContent` embeds your [...]
+
+That manual-by-design choice also draws a clean **permission** line. Viewing
the errors, listing the maps, and **resolving** a stack are all reads, gated by
`browser-errors:read`; **uploading or removing** a map is a write, gated by
`source-map:write`. So a read-only viewer can de-obfuscate stacks all day
without ever being able to change what maps are loaded — reading is reading,
mutating the map store is a write.
+
+## Where to go next
+
+For the field reference — the categories, the two provisioning paths, the
budgets and the matching-maps-to-builds guidance — see the [Browser Logs &
Source Maps
docs](https://skywalking.apache.org/docs/skywalking-horizon-ui/next/operate/browser-source-maps/).
+
+Next up: **Profiling** — five profilers (trace, async, eBPF, Go pprof,
network) rendered through one flame graph.
diff --git a/static/screenshots/horizon-0.7.0/p08-browser-01-stream.webp
b/static/screenshots/horizon-0.7.0/p08-browser-01-stream.webp
new file mode 100644
index 00000000000..ae6dffbca52
Binary files /dev/null and
b/static/screenshots/horizon-0.7.0/p08-browser-01-stream.webp differ
diff --git a/static/screenshots/horizon-0.7.0/p08-browser-02-resolve.webp
b/static/screenshots/horizon-0.7.0/p08-browser-02-resolve.webp
new file mode 100644
index 00000000000..7aff5a76af8
Binary files /dev/null and
b/static/screenshots/horizon-0.7.0/p08-browser-02-resolve.webp differ
diff --git
a/static/screenshots/horizon-0.7.0/p08-browser-03-sourcemap-manager.webp
b/static/screenshots/horizon-0.7.0/p08-browser-03-sourcemap-manager.webp
new file mode 100644
index 00000000000..7b1eae7329e
Binary files /dev/null and
b/static/screenshots/horizon-0.7.0/p08-browser-03-sourcemap-manager.webp differ