GT2/Ejectable/node_modules/expo/build/logs/LogSerialization.js

173 lines
6.4 KiB
JavaScript

import Constants from 'expo-constants';
import prettyFormat from 'pretty-format';
import parseErrorStack from 'react-native/Libraries/Core/Devtools/parseErrorStack';
import symbolicateStackTrace from 'react-native/Libraries/Core/Devtools/symbolicateStackTrace';
import ReactNodeFormatter from './format/ReactNodeFormatter';
export const EXPO_CONSOLE_METHOD_NAME = '__expoConsoleLog';
async function serializeLogDataAsync(data, level) {
let serializedValues;
let includesStack = false;
if (_stackTraceLogsSupported()) {
if (_isUnhandledPromiseRejection(data, level)) {
const rawStack = data[0];
const syntheticError = { stack: rawStack };
const stack = await _symbolicateErrorAsync(syntheticError);
if (!stack.length) {
serializedValues = _stringifyLogData(data);
}
else {
// NOTE: This doesn't handle error messages with newlines
const errorMessage = rawStack.split('\n')[1];
serializedValues = [
{
message: `[Unhandled promise rejection: ${errorMessage}]`,
stack: _formatStack(stack),
},
];
includesStack = true;
}
}
else if (data.length === 1 && data[0] instanceof Error) {
// When there's only one argument to the log function and that argument is an error, we
// include the error's stack. If there's more than one argument then we don't include the
// stack because it's not easy to display nicely in our current UI.
const serializedError = await _serializeErrorAsync(data[0]);
serializedValues = [serializedError];
includesStack = serializedError.hasOwnProperty('stack');
}
else if (level === 'warn' || level === 'error') {
// For console.warn and console.error it is usually useful to know the stack that leads to the
// warning or error, so we provide this information to help out with debugging
const error = _captureConsoleStackTrace();
// ["hello", "world"] becomes "hello, world"
const errorMessage = _stringifyLogData(data).join(', ');
const serializedError = await _serializeErrorAsync(error, errorMessage);
serializedValues = [serializedError];
includesStack = serializedError.hasOwnProperty('stack');
}
else {
serializedValues = _stringifyLogData(data);
}
}
else {
serializedValues = _stringifyLogData(data);
}
return {
body: [...serializedValues],
includesStack,
};
}
function _stringifyLogData(data) {
return data.map(item => {
// define the max length for log msg to be first 10000 characters
const LOG_MESSAGE_MAX_LENGTH = 10000;
const result = typeof item === 'string' ? item : prettyFormat(item, { plugins: [ReactNodeFormatter] });
// check the size of string returned
if (result.length > LOG_MESSAGE_MAX_LENGTH) {
let truncatedResult = result.substring(0, LOG_MESSAGE_MAX_LENGTH);
// truncate the result string to the max length
truncatedResult += `...(truncated to the first ${LOG_MESSAGE_MAX_LENGTH} characters)`;
return truncatedResult;
}
else {
return result;
}
});
}
async function _serializeErrorAsync(error, message) {
if (message == null) {
message = error.message;
}
if (!error.stack || !error.stack.length) {
return prettyFormat(error);
}
const stack = await _symbolicateErrorAsync(error);
const formattedStack = _formatStack(stack);
return { message, stack: formattedStack };
}
async function _symbolicateErrorAsync(error) {
const parsedStack = parseErrorStack(error);
let symbolicatedStack;
try {
// @ts-ignore: symbolicateStackTrace has different real/Flow declaration
// than the one in DefinitelyTyped.
symbolicatedStack = (await symbolicateStackTrace(parsedStack))?.stack ?? null;
}
catch (error) {
return parsedStack;
}
// In this context an unsymbolicated stack is better than no stack
if (!symbolicatedStack) {
return parsedStack;
}
// Clean the stack trace
return symbolicatedStack.map(_removeProjectRoot);
}
function _formatStack(stack) {
return stack
.map(frame => {
let line = `${frame.file}:${frame.lineNumber}`;
if (frame.column != null) {
line += `:${frame.column}`;
}
line += ` in ${frame.methodName}`;
return line;
})
.join('\n');
}
function _removeProjectRoot(frame) {
let filename = frame.file;
if (filename == null) {
return frame;
}
const projectRoot = _getProjectRoot();
if (projectRoot == null) {
return frame;
}
if (filename.startsWith(projectRoot)) {
filename = filename.substring(projectRoot.length);
if (filename[0] === '/' || filename[0] === '\\') {
filename = filename.substring(1);
}
frame.file = filename;
}
return frame;
}
/**
* Returns whether the development server that served this project supports logs with a stack trace.
* Specifically, the version of Expo CLI that includes `projectRoot` in the manifest also accepts
* payloads of the form:
*
* {
* includesStack: boolean, body: [{ message: string, stack: string }],
* }
*/
function _stackTraceLogsSupported() {
return !!(__DEV__ && _getProjectRoot());
}
function _isUnhandledPromiseRejection(data, level) {
return (level === 'warn' &&
typeof data[0] === 'string' &&
/^Possible Unhandled Promise Rejection/.test(data[0]));
}
function _captureConsoleStackTrace() {
try {
throw new Error();
}
catch (error) {
let stackLines = error.stack.split('\n');
const consoleMethodIndex = stackLines.findIndex(frame => frame.includes(EXPO_CONSOLE_METHOD_NAME));
if (consoleMethodIndex !== -1) {
stackLines = stackLines.slice(consoleMethodIndex + 1);
error.stack = stackLines.join('\n');
}
return error;
}
}
function _getProjectRoot() {
return Constants.manifest?.developer?.projectRoot ?? null;
}
export default {
serializeLogDataAsync,
};
//# sourceMappingURL=LogSerialization.js.map