Repository navigation
Description
ExceptionlessErrorHandler in @exceptionless/vue 3.2.1 adds the Vue component instance to the event with setProperty("vm", vm). Serializing a component instance walks $root, $parent, $el (DOM nodes), the app context and every reactive object reachable from them, so the first component error blocks the main thread for seconds and produces tens of megabytes of event data. In a real app the tab hangs and then crashes.
Where
packages/vue/src/index.ts#L11-L13:setProperty("vm", vm)packages/core/src/EventBuilder.ts#L184-L200:setPropertyrunsstringifysynchronouslypackages/core/src/Utils.ts#L489:stringifydefaults to a depth of 10, which does not bound the fan-out of a component graphpackages/vue/README.md#L35-L38: the README tells users to assign the handler toapp.config.errorHandler
What happens
Measured in Node 22 with jsdom, Vue 3.5 and @exceptionless/core 3.2.1, calling only the two setProperty calls the handler makes (before submit(), which serializes the event again and writes it to the persisted queue):
| Components on the page | setProperty("vm") time |
Serialized vm size |
|---|---|---|
| 2 | 0.5 s | 9.8 MB |
| 7 | 1.1 s | 23.7 MB |
| 22 | 1.9 s | 42.1 MB |
| 52 | 3.5 s | 80.7 MB (900 MB RSS) |
Results are the same with NODE_ENV=production. Vue also logs "Property "toJSON" was accessed during render but is not defined on instance" while the instance proxy is walked.
The handler's signature (err: Error, vm: unknown, info: unknown) => Promise<void> also doesn't match Vue's errorHandler type, so the documented app.config.errorHandler = ExceptionlessErrorHandler fails to compile in a strict TypeScript main.ts:
error TS2322: Type '(err: Error, vm: unknown, info: unknown) => Promise<void>' is not assignable to type '(err: unknown, instance: ComponentPublicInstance<...> | null, info: string) => void'.
Types of parameters 'err' and 'err' are incompatible.
Type 'unknown' is not assignable to type 'Error'.
Expected
A component error is reported quickly, with a small amount of data that identifies the component, and the page stays responsive.
Steps to reproduce
- Create a Vue 3 app, call
Exceptionless.startup(...)and setapp.config.errorHandler = ExceptionlessErrorHandleras shown in the README. - Render a component whose render function or setup throws.
- The tab stops responding on the first error, and on a page with more than a handful of components it runs out of memory.
Minimal Node repro of the serialization cost (uses vue, jsdom and @exceptionless/core@3.2.1):
import { JSDOM } from "jsdom";
const dom = new JSDOM("<!doctype html><div id=app></div>", { url: "http://localhost/" });
Object.assign(globalThis, { window: dom.window, document: dom.window.document, Element: dom.window.Element, SVGElement: dom.window.SVGElement });
const { createApp, h } = await import("vue");
const { ExceptionlessClient } = await import("@exceptionless/core");
const client = new ExceptionlessClient();
const Broken = { render() { throw new Error("boom"); } };
const app = createApp({ render: () => h("div", [h("span", "hi"), h(Broken)]) });
app.config.errorHandler = (err, vm, info) => {
const start = Date.now();
const builder = client.createException(err).setProperty("vm", vm).setProperty("info", info);
console.log(`${Date.now() - start}ms, ${JSON.stringify(builder.target.data.vm).length} bytes`);
};
app.mount("#app");Suggested fix
Don't serialize the instance. Record a few safe fields instead, such as the component name (vm?.$options?.name ?? vm?.$options?.__name) and info. Change the signature to (err: unknown, instance: ComponentPublicInstance | null, info: string) => void (submitting without returning the promise) so it can be assigned to app.config.errorHandler.
Activity