Skip to content

fix(demo): build and route client source maps so Faro can symbolicate stack traces#2047

Open
o6ivp wants to merge 2 commits intografana:mainfrom
o6ivp:fix/demo-faro-sourcemap-symbolication
Open

fix(demo): build and route client source maps so Faro can symbolicate stack traces#2047
o6ivp wants to merge 2 commits intografana:mainfrom
o6ivp:fix/demo-faro-sourcemap-symbolication

Conversation

@o6ivp
Copy link
Copy Markdown

@o6ivp o6ivp commented May 8, 2026

Why

The demo's vite build --outDir dist/client does not emit any *.js.map files because demo/vite.config.ts does not set build.sourcemap, so Vite's default (false) applies. Even if it did, Alloy's faro.receiver.sourcemaps { } is left as the empty default — it would fetch the source map from the URL the browser sends (http://localhost:5173/...), but localhost inside the Alloy container is the container's own loopback, not the demo's published port.

When a client-side error reaches Faro (e.g. by clicking the "Throw an error" button at /features/error-instrumentation), Alloy logs:

faro.receiver.integrations_app_agent_receiver
err="Get \"http://localhost:5173/assets/index-XXXXXX.js\":
     dial tcp [::1]:5173: connect: connection refused"

Stack traces still land in Loki, but minified — the developer cannot tell which .tsx file or line threw, defeating Faro's signature symbolication feature.

What

Two minimal, paired changes (and one Cypress regression test):

  • demo/vite.config.ts: set build.sourcemap: true so each chunk is emitted alongside its *.js.map.
  • infra/alloy/config/config.alloy: replace sourcemaps { } with a location block mapping the browser-facing URL prefix (http://localhost:${DEMO_PORT}/) to the compose-internal hostname (http://${DEMO_HOST}:${DEMO_PORT}). Both env vars are already defined in .env.
  • cypress/e2e/demo/sourceMaps.cy.ts: a new regression test that clicks the "Throw an error" button, reads the first stack frame's filename from the /collect POST, and cy.requests both that URL and its .map sibling. If build.sourcemap: true ever gets dropped from demo/vite.config.ts, the .map returns 404 and this test fails.

The two config changes are coupled — neither is useful without the other — so they ship together.

Verification

Run locally on main + this branch (and on top of #2035 / #2037 / #2038 to bring up the full stack).

Source maps are now built:

$ docker exec faro-sample-demo-1 ls /usr/app/demo/dist/client/assets/
index-Bhv74WHr.js
index-Bhv74WHr.js.map      ← new
index-Dese73mL.css
rolldown-runtime.rLFSEozg.js

Reachable from inside the Alloy container's network:

$ docker run --rm --network faro-sample_default alpine \
    sh -c 'apk add -q curl >/dev/null; curl -s -o /dev/null -w "%{http_code}" http://demo:5173/assets/index-Bhv74WHr.js.map'
200

Alloy parses the source map:

$ docker logs faro-sample-alloy-1 | grep "source map"
level=info msg="successfully parsed source map" subcomponent=handler
  url=http://localhost:5173/assets/index-Bhv74WHr.js

End-to-end symbolication — a Faro POST mimicking the demo's "Throw an error" path lands in Loki with the stack frame resolved to the original TypeScript source:

stacktrace="Error: This is a thrown error
  at ? (http://localhost:5173/src/client/pages/Features/ErrorInstrumentation.tsx:7:10)"

That matches demo/src/client/pages/Features/ErrorInstrumentation.tsx:7 where throw new Error('This is a thrown error'); lives.

Cypress regression suite (existing 5 errors tests + new sourceMaps test) all pass:

$ cypress run --headless --browser chrome --spec cypress/e2e/demo/{errors,sourceMaps}.cy.ts
✔  errors.cy.ts                             00:01        5        5        -        -        -
✔  sourceMaps.cy.ts                         00:01        1        1        -        -        -

The new test deliberately scopes to the browser-side prerequisite (sourcemap is built and served) because the existing CI matrix does not run Loki / Alloy / Tempo / Mimir. The Alloy-side URL rewrite is verified manually above and protected from accidental removal by the explicit location block.

Links

Closes #2043

Checklist

  • Tests added — Cypress regression test that fails if build.sourcemap is dropped
  • Changelog updated — handled by release-please via the conventional commit message
  • Documentation updated — N/A

o6ivp added 2 commits May 8, 2026 21:24
… stack traces

The demo's `vite build` doesn't emit source maps, and Alloy's
`faro.receiver.sourcemaps { }` is left as the default — so on a
client-side error Alloy logs:

    err="Get http://localhost:5173/.../...js: dial tcp [::1]:5173: connect: refused"

Two minimal changes:

- `demo/vite.config.ts`: set `build.sourcemap: true` so each chunk
  is emitted alongside its `*.js.map`.
- `infra/alloy/config/config.alloy`: add a `sourcemaps.location` block
  mapping the browser-facing `http://localhost:${DEMO_PORT}/` to the
  compose-internal `http://${DEMO_HOST}:${DEMO_PORT}`, reusing existing
  env vars.

After the fix, stack frames in Loki resolve to the original TypeScript
source (e.g. `ErrorInstrumentation.tsx:7:5`).

Closes grafana#2043.
Adds a Cypress regression test that clicks the "Throw an error" button
on `/features`, intercepts the `/collect` POST, reads the first stack
frame's filename, and `cy.request`s both that URL and its `.map`
sibling. If `build.sourcemap: true` ever gets dropped from
`demo/vite.config.ts`, the `.map` request returns 404 and this test
fails — preventing a silent regression of the Faro symbolication path.

The existing CI matrix (`yarn quality:e2e:ci`) does not run the LGTM
stack, so this test is intentionally limited to the browser-side
prerequisite (sourcemap is built and served). The Alloy-side URL
rewrite added in the same commit is not exercisable from CI today.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

demo: client source maps are not built or routed, so Faro can't symbolicate stack traces

1 participant