Repository navigation
T8: logException domain and skipFrames #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,11 @@ export interface CrashFrame { | |
| user: boolean; | ||
| data?: { source?: string; member?: string; line?: number; column?: number }; | ||
| debug_id?: string; | ||
| /** | ||
| * Excluded from the grouping location (the worker skips `hidden` frames when it picks the crash | ||
| * location) while still shown in the trace. Set by `logException({ skipFrames })`. | ||
| */ | ||
| hidden?: boolean; | ||
| /** | ||
| * Local variables in scope at this frame — `{ name: stringified-value }`, already scrubbed and capped | ||
| * by the capturing tier. Absent unless the platform captured them. | ||
|
|
@@ -51,6 +56,8 @@ export interface CrashProvenance { | |
| export interface CrashException { | ||
| name: string; | ||
| reason?: string; | ||
| /** Application-supplied exception domain (`logException({ domain })`; Android `ExceptionOptions.Domain`). */ | ||
| domain?: string; | ||
| frames: CrashFrame[]; | ||
| cause?: CrashException; | ||
| } | ||
|
|
@@ -293,6 +300,27 @@ function renderThrowable(value: unknown): string { | |
| return String(value); | ||
| } | ||
|
|
||
| /** | ||
| * Apply the already-validated `logException` options that live in the crash document: stamp `domain` on | ||
| * the outermost exception and mark its top `skipFrames` frames `hidden` (Android/iOS `skipFrames`: the | ||
| * frames stay in the trace but stop deciding the grouping location, so a wrapper or adapter does not | ||
| * collapse every error it reports into one issue). Mutates and returns `crash`. | ||
| */ | ||
| export function applyExceptionOptions( | ||
| crash: CrashJson, | ||
| options: { domain?: string; skipFrames?: number }, | ||
| ): CrashJson { | ||
| if (options.domain !== undefined) { | ||
| crash.exception.domain = options.domain; | ||
| } | ||
| if (options.skipFrames !== undefined) { | ||
| for (const frame of crash.exception.frames.slice(0, options.skipFrames)) { | ||
| frame.hidden = true; | ||
| } | ||
| } | ||
|
Comment on lines
+316
to
+320
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1 High — Problem: This loop hides It is not only the Impact: Wrapper authors who pass Scenario: function report(err: unknown) {
void client.logException(err, { skipFrames: 1 }); // JSDoc: skip the wrapper
}
await report('payment failed'); // non-Error → callSiteFrames already stripped logExceptionSynthetic / async stacks are commonly two frames: Fix: Clamp so at least one const frames = crash.exception.frames;
const lastUser = frames.findLastIndex((f) => f.user !== false);
const n = lastUser < 0 ? 0 : Math.min(options.skipFrames, lastUser);
for (const frame of frames.slice(0, n)) frame.hidden = true;Replace the |
||
| return crash; | ||
| } | ||
|
|
||
| export function buildCrashJson(error: unknown, options: BuildCrashOptions = {}): CrashJson { | ||
| if (!(error instanceof Error)) { | ||
| return { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2 Medium — JSDoc describes the
logExceptioncaller; the code skips the exception stackProblem: This comment says to use
skipFrameswhen a wrapper/adapter logs on the app's behalf, so errors do not group at the wrapper.applyExceptionOptionshidescrash.exception.frames[0..N)— the thrown value's frames, V8-order (throw /new Error()site first). A wrapper that doeslogException(caught)does not appear oncaught.stack. This repo's own adapters (express,fastify,reportError, …) correctly pass the original error with noskipFrames.Impact: Following this JSDoc on the common catch path hides the fault site. Remaining frames are often a shared caller (Express
Layer.handle, React render). Unrelated bugs collapse into one issue — the opposite of the stated goal.Scenario:
err.stackis the route that threw. Frame 0 (the actual bug) is markedhidden. Grouping moves to the next frame, frequently a framework frame shared by every request.skipFramesdoes skip the wrapper when the wrapper constructs the error (logException(new Error('x'), { skipFrames: 1 })) or logs a non-Error (synthetic frames fromcallSiteFrames). That is the AndroidExceptionSignaturemeaning; it is not “skip the function that calledlogException.”Fix: Document exception-stack semantics, e.g. “Hides the top N frames of the thrown value when picking the grouping location. Use when this call constructs the Error (or logs a non-Error) so grouping does not sit on this helper. Logging a caught error does not skip this call site — it is not on that stack.”