Skip to content

Commit

Permalink
add customizeStack hook (#36819)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #36819

X-link: facebook/metro#964

This diff creates a new hook to the Metro symbolicator. `customizeStack` aims to provide a whole stack modification hook on the output of the `/symbolicate` endpoint.

The purpose of this hook is to be able to apply callsite-based modifications to the stack trace. One such example is user-facing frame skipping APIs like FBLogger internally.

Consider the following API:

```
  FBLogger('my_project')
    .blameToPreviousFile()
    .mustfix(
      'This error should refer to the callsite of this method',
    );
```

In this particular case, we'd want to skip all frames from the top that come from the same source file. To do that, we need knowledge of the entire symbolicated stack, neither a hook before symbolication nor an implementation in `symbolicator.customizeFrame` are sufficient to be able to apply this logic.

This diff creates the new hook, which allows for mutations of the entire symbolicated stack via a `symbolicator.customizeStack` hook. The default implementation of this simply returns the same stack, but it can be wrapped similar to `symbolicator.customizeFrame`.

To actually have information for this hook to act on, I've created the possibility to send additional data to the metro `/symbolicate` endpoint via an `extraData` object. This mirrors the `extraData` from https://github.com/facebook/react-native/blob/main/packages/react-native/Libraries/Core/NativeExceptionsManager.js#L33, and I've wired up LogBox to send that object along with the symbolicate call.

Changelog:
[General][Added] - Added customizeStack hook to Metro's `/symbolicate` endpoint to allow custom frame skipping logic on a stack level.

Reviewed By: motiz88

Differential Revision: D44257733

fbshipit-source-id: 05cd57f5917a1e97b0520e772692ce64029fbf8a
  • Loading branch information
GijsWeterings authored and facebook-github-bot committed Apr 6, 2023
1 parent ad46bc6 commit 03e7801
Show file tree
Hide file tree
Showing 8 changed files with 27 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type SymbolicatedStackTrace = $ReadOnly<{

async function symbolicateStackTrace(
stack: Array<StackFrame>,
extraData?: mixed,
): Promise<SymbolicatedStackTrace> {
const devServer = getDevServer();
if (!devServer.bundleLoadedFromServer) {
Expand All @@ -41,7 +42,7 @@ async function symbolicateStackTrace(
const fetch = global.fetch ?? require('../../Network/fetch');
const response = await fetch(devServer.url + 'symbolicate', {
method: 'POST',
body: JSON.stringify({stack}),
body: JSON.stringify({stack, extraData}),
});
return await response.json();
}
Expand Down
5 changes: 4 additions & 1 deletion packages/react-native/Libraries/LogBox/Data/LogBoxLog.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export type LogBoxLogData = $ReadOnly<{|
componentStack: ComponentStack,
codeFrame?: ?CodeFrame,
isComponentError: boolean,
extraData?: mixed,
|}>;

class LogBoxLog {
Expand All @@ -43,6 +44,7 @@ class LogBoxLog {
level: LogLevel;
codeFrame: ?CodeFrame;
isComponentError: boolean;
extraData: mixed | void;
symbolicated:
| $ReadOnly<{|error: null, stack: null, status: 'NONE'|}>
| $ReadOnly<{|error: null, stack: null, status: 'PENDING'|}>
Expand All @@ -62,6 +64,7 @@ class LogBoxLog {
this.componentStack = data.componentStack;
this.codeFrame = data.codeFrame;
this.isComponentError = data.isComponentError;
this.extraData = data.extraData;
this.count = 1;
}

Expand Down Expand Up @@ -91,7 +94,7 @@ class LogBoxLog {
handleSymbolicate(callback?: (status: SymbolicationStatus) => void): void {
if (this.symbolicated.status !== 'PENDING') {
this.updateStatus(null, null, null, callback);
LogBoxSymbolication.symbolicate(this.stack).then(
LogBoxSymbolication.symbolicate(this.stack, this.extraData).then(
data => {
this.updateStatus(null, data?.stack, data?.codeFrame, callback);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,13 @@ export function deleteStack(stack: Stack): void {
cache.delete(stack);
}

export function symbolicate(stack: Stack): Promise<SymbolicatedStackTrace> {
export function symbolicate(
stack: Stack,
extraData?: mixed,
): Promise<SymbolicatedStackTrace> {
let promise = cache.get(stack);
if (promise == null) {
promise = symbolicateStackTrace(stack).then(sanitize);
promise = symbolicateStackTrace(stack, extraData).then(sanitize);
cache.set(stack, promise);
}

Expand Down
7 changes: 7 additions & 0 deletions packages/react-native/Libraries/LogBox/Data/parseLogBoxLog.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export function parseLogBoxException(
substitutions: [],
},
category: `${fileName}-${row}-${column}`,
extraData: error.extraData,
};
}

Expand Down Expand Up @@ -238,6 +239,7 @@ export function parseLogBoxException(
substitutions: [],
},
category: `${fileName}-${row}-${column}`,
extraData: error.extraData,
};
}

Expand All @@ -261,6 +263,7 @@ export function parseLogBoxException(
substitutions: [],
},
category: `${fileName}-${1}-${1}`,
extraData: error.extraData,
};
}

Expand All @@ -275,6 +278,7 @@ export function parseLogBoxException(
substitutions: [],
},
category: message,
extraData: error.extraData,
};
}

Expand All @@ -286,6 +290,7 @@ export function parseLogBoxException(
isComponentError: error.isComponentError,
componentStack:
componentStack != null ? parseComponentStack(componentStack) : [],
extraData: error.extraData,
...parseInterpolation([message]),
};
}
Expand All @@ -297,6 +302,7 @@ export function parseLogBoxException(
stack: error.stack,
isComponentError: error.isComponentError,
componentStack: parseComponentStack(componentStack),
extraData: error.extraData,
...parseInterpolation([message]),
};
}
Expand All @@ -307,6 +313,7 @@ export function parseLogBoxException(
level: 'error',
stack: error.stack,
isComponentError: error.isComponentError,
extraData: error.extraData,
...parseLogBoxLog([message]),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ exports[`LogBoxContainer should render fatal with selectedIndex 2 1`] = `
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"extraData": undefined,
"isComponentError": false,
"level": "fatal",
"message": Object {
Expand Down Expand Up @@ -71,6 +72,7 @@ exports[`LogBoxContainer should render warning with selectedIndex 0 1`] = `
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"extraData": undefined,
"isComponentError": false,
"level": "warn",
"message": Object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ exports[`LogBoxNotificationContainer should render both an error and warning not
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"extraData": undefined,
"isComponentError": false,
"level": "warn",
"message": Object {
Expand Down Expand Up @@ -65,6 +66,7 @@ exports[`LogBoxNotificationContainer should render both an error and warning not
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"extraData": undefined,
"isComponentError": false,
"level": "error",
"message": Object {
Expand Down Expand Up @@ -124,6 +126,7 @@ exports[`LogBoxNotificationContainer should render the latest error notification
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"extraData": undefined,
"isComponentError": false,
"level": "error",
"message": Object {
Expand Down Expand Up @@ -175,6 +178,7 @@ exports[`LogBoxNotificationContainer should render the latest warning notificati
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"extraData": undefined,
"isComponentError": false,
"level": "warn",
"message": Object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ exports[`LogBoxNotificationContainer should render inspector with logs, even whe
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"extraData": undefined,
"isComponentError": false,
"level": "warn",
"message": Object {
Expand All @@ -39,6 +40,7 @@ exports[`LogBoxNotificationContainer should render inspector with logs, even whe
"codeFrame": undefined,
"componentStack": Array [],
"count": 1,
"extraData": undefined,
"isComponentError": false,
"level": "error",
"message": Object {
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/types/modules/Devtools.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ declare module 'react-native/Libraries/Core/Devtools/symbolicateStackTrace' {

export default function symbolicateStackTrace(
stack: ReadonlyArray<StackFrame>,
extraData?: any,
): Promise<StackFrame[]>;
}

0 comments on commit 03e7801

Please sign in to comment.