-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement better reanimated logger with clean stack traces (#6385)
This PR is a much cleaner approach than proposed in #6364. It includes metro-config modification which is essential to collapse logs from reanimated source code, which aren't helpful to the user while tracking down the issue. The previous approach was trimming logs from reanimated source code completely - this approach just collapses them, so that they are still available to the user and can be revealed above the presented stack trace part. To get better logs, I had to implement the following 2 changes: 1. **metro config** - the `symbolicator` must have been added to properly collapse stack frames that aren't meaningful to the user, 2. **logger object** - the new logger object uses `LogBox.addLog` method, thanks to which we can get pretty stack traces when we log a warning from the UI thread (before such warnings didn't include meaningful stack trace as error stack was created inside `LogBox` after `runOnJS` was called, so we were getting a bit limited JS stack - see [example 11](#6387 (comment)) in the follow up PR). (tested on a real project to see if it works there as well) - current logs either point to the reanimated source code or aren't readable at all (if warning is logged from the UI thread as in the example below) - new logger shows correct destination of the issue culprit in the code frame, collapses stack frames in the call stack that aren't interesting to the user (reanimated source code) and focuses on the file where the user included problematic code | Before | After | |-|-| | <video src="https://github.com/user-attachments/assets/a5302586-f4d0-4636-8bd8-6c406c9d8c73" /> | <video src="https://github.com/user-attachments/assets/3121636f-69a2-4b6f-8f38-b1889d4c62e1" /> | See the example in the next PR (#6387). --------- Co-authored-by: Tomasz Żelawski <[email protected]>
- Loading branch information
Showing
17 changed files
with
278 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import type { ConfigT } from 'metro-config'; | ||
|
||
export declare function wrapWithReanimatedMetroConfig(config: ConfigT): ConfigT; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
const COLLAPSED_STACK_REGEX = new RegExp( | ||
[ | ||
// For internal usage in the example app | ||
'/packages/react-native-reanimated/.+\\.(t|j)sx?$', | ||
// When reanimated is installed as a dependency (node_modules) | ||
'/node_modules/react-native-reanimated/.+\\.(t|j)sx?$', | ||
] | ||
// Make patterns work with both Windows and POSIX paths. | ||
.map((pathPattern) => pathPattern.replaceAll('/', '[/\\\\]')) | ||
.join('|') | ||
); | ||
|
||
function wrapWithReanimatedMetroConfig(config) { | ||
return { | ||
...config, | ||
symbolicator: { | ||
async customizeFrame(frame) { | ||
const collapse = Boolean( | ||
// Collapse the stack frame based on user's config symbolicator settings | ||
(await config?.symbolicator?.customizeFrame?.(frame))?.collapse || | ||
// or, if not already collapsed, collapse the stack frame with path | ||
// to react-native-reanimated source code | ||
(frame.file && COLLAPSED_STACK_REGEX.test(frame.file)) | ||
); | ||
return { | ||
collapse, | ||
}; | ||
}, | ||
}, | ||
}; | ||
} | ||
|
||
module.exports = { | ||
wrapWithReanimatedMetroConfig, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -116,6 +116,7 @@ const notCapturedIdentifiers = [ | |
|
||
// Reanimated | ||
'_WORKLET', | ||
'ReanimatedError', | ||
]; | ||
|
||
/** | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
'use strict'; | ||
/** | ||
* Copied from: | ||
* react-native/Libraries/LogBox/Data/LogBoxData.js | ||
* react-native/Libraries/LogBox/Data/parseLogBoxLog.js | ||
*/ | ||
|
||
import type { LogBoxStatic } from 'react-native'; | ||
import { LogBox as RNLogBox } from 'react-native'; | ||
|
||
export type LogLevel = 'warn' | 'error' | 'fatal' | 'syntax'; | ||
|
||
type Message = { | ||
content: string; | ||
substitutions: { length: number; offset: number }[]; | ||
}; | ||
|
||
type Category = string; | ||
|
||
interface Location { | ||
row: number; | ||
column: number; | ||
} | ||
|
||
interface CodeFrame { | ||
content: string; | ||
location?: Location | null; | ||
fileName: string; | ||
collapse?: boolean; | ||
} | ||
|
||
type ComponentStack = CodeFrame[]; | ||
|
||
type ComponentStackType = 'legacy' | 'stack'; | ||
|
||
export type LogData = { | ||
level: LogLevel; | ||
message: Message; | ||
category: Category; | ||
componentStack: ComponentStack; | ||
componentStackType: ComponentStackType | null; | ||
stack?: string; | ||
}; | ||
|
||
interface LogBoxExtended extends LogBoxStatic { | ||
addLog(data: LogData): void; | ||
} | ||
|
||
const LogBox = RNLogBox as LogBoxExtended; | ||
|
||
const noop = () => { | ||
// do nothing | ||
}; | ||
|
||
// Do nothing when addLogBoxLog is called if LogBox is not available | ||
export const addLogBoxLog = LogBox?.addLog?.bind(LogBox) ?? noop; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
'use strict'; | ||
export * from './logger'; | ||
export * from './LogBox'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
'use strict'; | ||
import { addLogBoxLog } from './LogBox'; | ||
import type { LogLevel, LogData } from './LogBox'; | ||
|
||
function logToConsole(data: LogData) { | ||
'worklet'; | ||
switch (data.level) { | ||
case 'warn': | ||
console.warn(data.message.content); | ||
break; | ||
case 'error': | ||
case 'fatal': | ||
case 'syntax': | ||
console.error(data.message.content); | ||
break; | ||
} | ||
} | ||
|
||
function formatMessage(message: string) { | ||
'worklet'; | ||
return `[Reanimated] ${message}`; | ||
} | ||
|
||
function createLog(level: LogLevel, message: string): LogData { | ||
'worklet'; | ||
const formattedMessage = formatMessage(message); | ||
|
||
return { | ||
level, | ||
message: { | ||
content: formattedMessage, | ||
substitutions: [], | ||
}, | ||
category: formattedMessage, | ||
componentStack: [], | ||
componentStackType: null, | ||
stack: new Error().stack, | ||
}; | ||
} | ||
|
||
const loggerImpl = { | ||
logFunction: logToConsole, | ||
}; | ||
|
||
/** | ||
* Function that logs to LogBox and console. | ||
* Used to replace the default console logging with logging to LogBox | ||
* on the UI thread when runOnJS is available. | ||
* | ||
* @param data - The details of the log. | ||
*/ | ||
export function logToLogBoxAndConsole(data: LogData) { | ||
addLogBoxLog(data); | ||
logToConsole(data); | ||
} | ||
|
||
/** | ||
* Replaces the default log function with a custom implementation. | ||
* | ||
* @param logFunction - The custom log function. | ||
*/ | ||
export function replaceLoggerImplementation( | ||
logFunction: (data: LogData) => void | ||
) { | ||
loggerImpl.logFunction = logFunction; | ||
} | ||
|
||
export const logger = { | ||
warn(message: string) { | ||
'worklet'; | ||
loggerImpl.logFunction(createLog('warn', message)); | ||
}, | ||
error(message: string) { | ||
'worklet'; | ||
loggerImpl.logFunction(createLog('error', message)); | ||
}, | ||
fatal(message: string) { | ||
'worklet'; | ||
loggerImpl.logFunction(createLog('fatal', message)); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.