UNPKG

7.1 kBJavaScriptView Raw
1import Constants from 'expo-constants';
2import prettyFormat from 'pretty-format';
3import parseErrorStack from 'react-native/Libraries/Core/Devtools/parseErrorStack';
4import symbolicateStackTrace from 'react-native/Libraries/Core/Devtools/symbolicateStackTrace';
5import ReactNodeFormatter from './format/ReactNodeFormatter';
6export const EXPO_CONSOLE_METHOD_NAME = '__expoConsoleLog';
7async function serializeLogDataAsync(data, level) {
8 let serializedValues;
9 let includesStack = false;
10 if (_stackTraceLogsSupported()) {
11 if (_isUnhandledPromiseRejection(data, level)) {
12 const rawStack = data[0];
13 const syntheticError = { stack: rawStack };
14 const stack = await _symbolicateErrorAsync(syntheticError);
15 if (!stack.length) {
16 serializedValues = _stringifyLogData(data);
17 }
18 else {
19 // NOTE: This doesn't handle error messages with newlines
20 const errorMessage = rawStack.split('\n')[1];
21 serializedValues = [
22 {
23 message: `[Unhandled promise rejection: ${errorMessage}]`,
24 stack: _formatStack(stack),
25 },
26 ];
27 includesStack = true;
28 }
29 }
30 else if (data.length === 1 && data[0] instanceof Error) {
31 // When there's only one argument to the log function and that argument is an error, we
32 // include the error's stack. If there's more than one argument then we don't include the
33 // stack because it's not easy to display nicely in our current UI.
34 const serializedError = await _serializeErrorAsync(data[0]);
35 serializedValues = [serializedError];
36 includesStack = serializedError.hasOwnProperty('stack');
37 }
38 else if (level === 'warn' || level === 'error') {
39 // For console.warn and console.error it is usually useful to know the stack that leads to the
40 // warning or error, so we provide this information to help out with debugging
41 const error = _captureConsoleStackTrace();
42 // ["hello", "world"] becomes "hello, world"
43 const errorMessage = _stringifyLogData(data).join(', ');
44 const serializedError = await _serializeErrorAsync(error, errorMessage);
45 serializedValues = [serializedError];
46 includesStack = serializedError.hasOwnProperty('stack');
47 }
48 else {
49 serializedValues = _stringifyLogData(data);
50 }
51 }
52 else {
53 serializedValues = _stringifyLogData(data);
54 }
55 return {
56 body: [...serializedValues],
57 includesStack,
58 };
59}
60function _stringifyLogData(data) {
61 return data.map(item => {
62 if (typeof item === 'string') {
63 return item;
64 }
65 else {
66 // define the max length for log msg to be first 10000 characters
67 const LOG_MESSAGE_MAX_LENGTH = 10000;
68 const result = prettyFormat(item, { plugins: [ReactNodeFormatter] });
69 // check the size of string returned
70 if (result.length > LOG_MESSAGE_MAX_LENGTH) {
71 let truncatedResult = result.substring(0, LOG_MESSAGE_MAX_LENGTH);
72 // truncate the result string to the max length
73 truncatedResult += `...(truncated to the first ${LOG_MESSAGE_MAX_LENGTH} characters)`;
74 return truncatedResult;
75 }
76 else {
77 return result;
78 }
79 }
80 });
81}
82async function _serializeErrorAsync(error, message) {
83 if (message == null) {
84 message = error.message;
85 }
86 // note(brentvatne): React Native currently appends part of the stack inside of
87 // the error message itself for some reason. This is just confusing and we don't
88 // want to include it in the expo-cli output
89 const messageParts = message.split('\n');
90 const firstUselessLine = messageParts.indexOf('This error is located at:');
91 if (firstUselessLine > 0) {
92 message = messageParts.slice(0, firstUselessLine - 1).join('\n');
93 }
94 if (!error.stack || !error.stack.length) {
95 return prettyFormat(error);
96 }
97 const stack = await _symbolicateErrorAsync(error);
98 const formattedStack = _formatStack(stack);
99 return { message, stack: formattedStack };
100}
101async function _symbolicateErrorAsync(error) {
102 const parsedStack = parseErrorStack(error);
103 let symbolicatedStack;
104 try {
105 // @ts-ignore: symbolicateStackTrace has different real/Flow declaration
106 // than the one in DefinitelyTyped.
107 symbolicatedStack = (await symbolicateStackTrace(parsedStack))?.stack ?? null;
108 }
109 catch (error) {
110 return parsedStack;
111 }
112 // In this context an unsymbolicated stack is better than no stack
113 if (!symbolicatedStack) {
114 return parsedStack;
115 }
116 // Clean the stack trace
117 return symbolicatedStack.map(_removeProjectRoot);
118}
119function _formatStack(stack) {
120 return stack
121 .map(frame => {
122 let line = `${frame.file}:${frame.lineNumber}`;
123 if (frame.column != null) {
124 line += `:${frame.column}`;
125 }
126 line += ` in ${frame.methodName}`;
127 return line;
128 })
129 .join('\n');
130}
131function _removeProjectRoot(frame) {
132 let filename = frame.file;
133 if (filename == null) {
134 return frame;
135 }
136 const projectRoot = _getProjectRoot();
137 if (projectRoot == null) {
138 return frame;
139 }
140 if (filename.startsWith(projectRoot)) {
141 filename = filename.substring(projectRoot.length);
142 if (filename[0] === '/' || filename[0] === '\\') {
143 filename = filename.substring(1);
144 }
145 frame.file = filename;
146 }
147 return frame;
148}
149/**
150 * Returns whether the development server that served this project supports logs with a stack trace.
151 * Specifically, the version of Expo CLI that includes `projectRoot` in the manifest also accepts
152 * payloads of the form:
153 *
154 * {
155 * includesStack: boolean, body: [{ message: string, stack: string }],
156 * }
157 */
158function _stackTraceLogsSupported() {
159 return !!(__DEV__ && _getProjectRoot());
160}
161function _isUnhandledPromiseRejection(data, level) {
162 return (level === 'warn' &&
163 typeof data[0] === 'string' &&
164 /^Possible Unhandled Promise Rejection/.test(data[0]));
165}
166function _captureConsoleStackTrace() {
167 try {
168 throw new Error();
169 }
170 catch (error) {
171 let stackLines = error.stack.split('\n');
172 const consoleMethodIndex = stackLines.findIndex(frame => frame.includes(EXPO_CONSOLE_METHOD_NAME));
173 if (consoleMethodIndex !== -1) {
174 stackLines = stackLines.slice(consoleMethodIndex + 1);
175 error.stack = stackLines.join('\n');
176 }
177 return error;
178 }
179}
180function _getProjectRoot() {
181 return Constants.manifest?.developer?.projectRoot ?? null;
182}
183export default {
184 serializeLogDataAsync,
185};
186//# sourceMappingURL=LogSerialization.js.map
\No newline at end of file