super-log for VS Code
Bring the super-log bench into the
editor, and forward what the editor already knows — build output and debug
output — to the hub as bench events, with no wrapper scripts and no copy-paste.
- Auto-capture builds. CMake / Make / Ninja (and anything else you point it
at) are teed to the hub as
vscode.build.<task> events, level-classified the
way a compiler talks: error: → ERROR, warning: → WARN, everything else
INFO. Ninja/CMake progress rides as a build.progress_pct metric.
- Auto-capture debug. Every debug session's console output becomes
vscode.debug.<session> events (DAP output, classified by category —
stderr is error-ish), and session start/stop bookend it.
- The viewer, in a tab.
super-log: Open Bench Viewer opens a theme-aware
panel that subscribes to the hub's firehose and renders the log stream and an
alarms strip, light/dark-correct.
- Ergonomics. A status-bar indicator (connected / events sent / hub down), a
one-click "run a build and capture it", and a "start the hub" command for when
superlogd is not answering.
Zero runtime dependencies. The extension host speaks to the hub over plain
node:http; the viewer uses the webview's own WebSocket. The only build tool
is esbuild.
Design note
Capturing build output — why a task wrapper
VS Code's stable API has no way to read the text an arbitrary task writes
to its terminal. onDidStartTask / onDidEndTaskProcess give you the task, the
command line and the exit code — never the body. window.onDidWriteTerminalData
would give the bytes, but it is a proposed API a published extension may not
ship against. So capture is done in two honest layers:
Full capture — the superlog task type. The extension contributes a task
type whose CustomExecution returns a pseudoterminal. The pseudoterminal
spawns the build command itself, so the extension owns the child's
stdout/stderr: each line is written to the terminal (the task looks and feels
normal) and teed to the hub, classified by level. This is the "spawn the
build through a wrapper that tees to the hub" approach, exposed as a
first-class task so it can be a preLaunchTask, the default build task, or
run from the Tasks menu. A workspace with a CMakeLists.txt, Makefile or
build.ninja gets these tasks auto-provided.
Add one to tasks.json:
{
"type": "superlog",
"label": "cmake build",
"command": "cmake --build build",
"group": "build"
}
or just run super-log: Run Build and Capture to Hub and type a command
(prefilled from superlog.defaultBuildCommand).
Lifecycle capture — every other task. For a build run by
ms-vscode.cmake-tools or any other extension, whose output we cannot read,
the task-process listeners still emit a start event and an
edge-triggered verdict (INFO on exit 0, ERROR otherwise) on the same
vscode.build.<task> topic. Partial but truthful: you always see on the
bench that a build ran and how it ended, and if you want the per-line
diagnostics too, route that build through a superlog task.
By default this layer fires for tasks in the Build group or whose command
looks like a compiler/build invocation (cmake, make, ninja, msbuild,
xcodebuild, cargo build, go build, tsc, gradle, clang/gcc/…). Set
superlog.capture.allTasks to widen it to every task.
A start and its verdict share a trace, so "show me everything from that build"
is one filter in any viewer.
Capturing debug output — the DAP tracker
vscode.debug.registerDebugAdapterTrackerFactory('*', …) sits between VS Code
and every debug adapter and sees the raw Debug Adapter Protocol traffic. The
output event is the debug console, so each one is forwarded to
vscode.debug.<session>, classified by DAP category (stderr → ERROR,
important → WARN, stdout/console → INFO unless the text itself is a
compiler-shaped error/warning). Session start/stop ride the session lifecycle
events (so a silent session still bookends), and an adapter that errors or exits
non-zero surfaces as an ERROR rather than vanishing. All events in one session
share the session id as their trace.
The viewer — native webview, with an opt-in React path
A VS Code webview runs in its own origin (vscode-webview://…), so it cannot
reach ws://127.0.0.1:7333 unless the page's Content-Security-Policy names that
origin in connect-src. The extension sets connect-src from the configured
hub URL (plus loopback fallbacks), and from there the webview's own WebSocket
talks straight to the hub — nothing is proxied through the extension host.
The shipped viewer is a native panel (media/viewer.js), a faithful, small
reimplementation of the viewer half of the wire protocol — parse the /ws
envelope, split the NDJSON payload, apply the tolerant-reader rule, seed from
/recent?snapshot=1 — themed entirely with VS Code's own --vscode-* CSS
variables, so it is light/dark-correct and looks native. Log content reaches the
DOM only through textContent, never innerHTML: a log line is data, never
markup, and can never inject into the panel.
Why not drop in viewer/react directly? It cannot be embedded unmodified:
it derives the hub from window.location (which, inside a webview, is the
vscode-webview origin — no :7333 there), it ships absolute /assets/…
paths that a webview will not serve, and its optional panels reach services
(the alarm gateway, the paid interpreter) that need not be running. Rebuilding
it here would also mean either running its Vite toolchain (its node_modules is
empty in a fresh clone) or modifying viewer/react — both out of scope for this
extension, which stays entirely within editors/vscode/.
So the React app is an opt-in path instead of the default. Set
superlog.viewer.useReactBundle: true and populate media/react/ with a build
of the viewer, and the panel will load it — the loader rewrites its absolute
asset paths to webview URIs and injects the hub via ?hub= (a bootstrap that
runs before the app's deferred module reads window.location.search). To
produce that build without touching the upstream tree:
# from a super-log clone, with the React viewer's deps installed once:
npm --prefix viewer/react install
npm --prefix viewer/react exec -- vite build --base ./ \
--outDir "$PWD/editors/vscode/media/react" --emptyOutDir
If media/react/ is absent, the panel falls back to the native viewer with a
notice. The tradeoff is deliberate: the native panel always works and always
compiles; the React path gives the full board (servers, devices, topology,
versions, agents, PRs, RPC, webhooks) at the cost of a manual build step.
Commands
| Command |
What |
super-log: Open Bench Viewer |
Open the viewer panel (also the status-bar click) |
super-log: Run Build and Capture to Hub |
Run a command through the tee wrapper |
super-log: Start Hub (superlogd) |
Launch superlogd if /healthz does not answer |
super-log: Toggle Auto-Capture (build + debug) |
Flip both auto-capture switches |
super-log: Show Extension Log |
Open the extension's output channel |
Settings
| Setting |
Default |
Meaning |
superlog.hubUrl |
http://127.0.0.1:7333 |
Hub base URL (ingest + /ws + /recent) |
superlog.autoCapture.build |
true |
Forward build output and verdicts |
superlog.autoCapture.debug |
true |
Forward debug console and session lifecycle |
superlog.capture.allTasks |
false |
Lifecycle events for every task, not just builds |
superlog.topics.buildPrefix |
vscode.build |
Topic prefix for build events |
superlog.topics.debugPrefix |
vscode.debug |
Topic prefix for debug events |
superlog.defaultBuildCommand |
cmake --build build |
Prefilled for the capture command |
superlog.hubPath |
"" |
Path to superlogd; empty auto-detects build/hub/superlogd |
superlog.maxQueue |
5000 |
Bounded event queue; oldest dropped under burst |
superlog.viewer.useReactBundle |
false |
Load the opt-in React bundle from media/react/ |
Under a burst the queue drops the oldest events rather than stalling the
editor — a logger that can stall what it observes is worse than no logger, the
same bounded-queue rule every SDK on the bench follows.
Topics on the wire
Events are the v1 schema from
docs/PROTOCOL.md, origin.runtime = vscode:
vscode.build.<task> — one event per build line (tag stdout/stderr), plus
a build.progress_pct metric where the toolchain prints progress, plus a
start event and a verdict.
vscode.debug.<session> — one event per DAP output line, plus session
start/stop and adapter error/exit.
Build and run
cd editors/vscode
npm install
npm run compile # -> dist/extension.js (esbuild)
# then press F5 in VS Code (Extension Development Host), or:
npm run package # -> a .vsix (needs @vscode/vsce)
npm run watch rebuilds on change; npm run typecheck runs tsc --noEmit.
License
MIT © Saxon Herschel Nicholls. Part of super-log.