跪拜 Guibai
← All articles
Interviews · Frontend · Architecture

A Vue Plugin That Reconstructs User Sessions Without the RUM Tax

By 阳光是sunny · · 107 views · 2 likes
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Most teams either skip frontend observability or reach for a commercial RUM product that adds latency, cost, and vendor lock-in. This plugin gives you the one thing that actually solves incidents — a breadcrumb trail with a shared traceId — in under 200 lines, with privacy and volume controls built in from the start.

Summary

A single `createBehaviorMonitor` plugin mounts globally in a Vue 3 app and collects the full user journey: route enter/leave, click targets (auto-resolved from `data-track` or innerText), unhandled JS and Promise errors, and Axios responses exceeding a 3-second threshold. Every event carries a session-scoped traceId, a page path, and a timestamp, so backend logs can reconstruct exactly what the user did before a failure. The plugin queues events in memory and flushes them in batches on an 8-second timer, on errors, or on `beforeunload`, using `sendBeacon` with a `fetch` keepalive fallback.

To keep log volume sane, the approach layers four frequency controls: sampling non-critical clicks at 20–30%, debouncing repeated clicks on the same target within 800ms, restricting collection to a whitelist of core business pages, and skipping input values entirely — only field names are recorded. Critical buttons get explicit `data-track` attributes and are always collected. An Axios interceptor wrapper timestamps every request and fires a tracked event when a call takes longer than 3 seconds or fails outright, attaching the HTTP status and error message.

A single traceId then ties together the click that triggered a request, the slow or failed API call, and the route the user was on, turning a vague "button didn't work" report into a concrete timeline the backend can correlate with its own logs.

Takeaways
Every event carries a session-scoped traceId, current page path, timestamp, and user-agent string, so backend logs can correlate a click, a route change, and a failed API call into one timeline.
The plugin queues events in memory (default max 30) and flushes them on an 8-second timer, on any error-level event, and on `beforeunload`, using `sendBeacon` with a `fetch` keepalive fallback.
Click targets are resolved by walking up the DOM: `data-track` attributes take priority, then innerText trimmed to 40 characters, then the element's tagName.
An Axios wrapper timestamps every request via `config.metadata.start` and fires a tracked event when a response exceeds 3 seconds or when a request fails, attaching the URL, method, cost, and HTTP status.
Four frequency controls keep log volume manageable: sample non-critical clicks at 20–30%, debounce repeated clicks on the same target within 800ms, restrict collection to a whitelist of core pages, and never record input values — only field names.
Core business buttons must carry explicit `data-track` attributes (e.g., `order.pay.submit` or `order.refund.${orderId}`); generic interactions can rely on auto-collected innerText.
A single traceId lets the backend reconstruct a full sequence: user enters `/order/submit` → clicks `order.pay.submit` → calls `/api/pay/create` → 504 timeout after 12 seconds.
Conclusions

Most frontend observability debates miss the point: the goal is not exhaustive logging but reconstructing the user's last N steps before a failure. A 30-event ring buffer with a traceId does that; a million-row click table does not.

The plugin's architecture — queue locally, flush on a timer or on error — mirrors what expensive RUM products do under the hood, but without the CDN dependency, the sampling bias, or the per-event pricing.

Privacy compliance is handled by design, not by policy: input values are never captured, only field names. That sidesteps the GDPR/CCPA headaches that sink many homegrown monitoring efforts.

Explicit `data-track` attributes on critical buttons are the difference between knowing 'someone clicked Delete' and knowing 'someone clicked Delete on order #45821.' The latter lets you reproduce the bug; the former is noise.

The Axios interceptor pattern — attaching a start timestamp to `config.metadata` — is a zero-dependency trick that works with any HTTP client, not just Axios, and avoids polluting request payloads.

Sampling non-critical clicks at 30% and debouncing repeated clicks is a pragmatic middle ground: it preserves enough signal to spot UX dead zones while keeping the event stream cheap enough to run on a modest backend.

Concepts & terms
sendBeacon
A browser API that sends a small amount of data asynchronously to a server, guaranteed to run even if the page is unloading. Ideal for flushing analytics or error logs on `beforeunload` without blocking navigation.
Breadcrumb (in monitoring)
A chronological trail of the user's last N actions (clicks, route changes, API calls) leading up to an error. Unlike a full event log, a breadcrumb is a small, focused window that helps reconstruct the user's exact path during an incident.
traceId
A unique identifier generated per browser session and attached to every logged event. It lets backend systems correlate frontend clicks, route transitions, and API calls into a single user timeline across distributed services.
RUM (Real User Monitoring)
A category of tools that collect performance and behavior data from actual users' browsers. Commercial RUM products often inject a heavy JavaScript agent; a lightweight plugin can capture the same diagnostic signal with far less overhead.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗