import {memo} from 'react';
import isObject from 'lodash/isObject';
import {OnExpandCallback} from 'sentry/components/objectInspector';
import {objectIsEmpty} from 'sentry/utils';
import type {BreadcrumbFrame, ConsoleFrame} from 'sentry/utils/replays/types';
import {isConsoleFrame} from 'sentry/utils/replays/types';
import Format from 'sentry/views/replays/detail/console/format';
interface Props {
frame: BreadcrumbFrame;
expandPaths?: string[];
onExpand?: OnExpandCallback;
}
// There is a special case where `console.error()` is called with an Error object.
// The SDK uses the Error's `message` property as the breadcrumb message, but we lose the Error type,
// resulting in an empty object in the breadcrumb arguments.
//
// In this special case, we re-create the error object
function isSerializedError(frame: ConsoleFrame) {
const args = frame.data.arguments;
return (
frame.message &&
typeof frame.message === 'string' &&
Array.isArray(args) &&
args.length <= 2 &&
isObject(args[0]) &&
objectIsEmpty(args[0])
);
}
/**
* Attempt to emulate the browser console as much as possible
*/
function UnmemoizedMessageFormatter({frame, expandPaths, onExpand}: Props) {
if (!isConsoleFrame(frame)) {
return (
);
}
const args = frame.data.arguments;
// Turn this back into an Error object so can pretty print it
if (args && isSerializedError(frame)) {
// Sometimes message can include stacktrace
const splitMessage = frame.message.split('\n');
const errorMessagePiece = splitMessage[0].trim();
// Error.prototype.toString() will prepend the error type meaning it will
// not be the same as `message` property. We want message only when
// creating a new Error instance, otherwise the type will repeat.
const errorMessageSplit = errorMessagePiece.split('Error: ');
// Restitch together in case there were other `Error: ` strings in the message
const errorMessage = errorMessageSplit
.splice(errorMessageSplit.length - 1)
.join('Error: ');
const fakeError = new Error(errorMessage);
try {
// Messages generally do not include stack trace due to SDK serialization
fakeError.stack = args.length === 2 ? (args[1] as string) : undefined;
// Re-create the error name
if (errorMessageSplit.length > 1) {
fakeError.name = errorMessageSplit[0] + 'Error: ';
}
} catch {
// Some browsers won't allow you to write to error properties
}
return ;
}
return (
);
}
const MessageFormatter = memo(UnmemoizedMessageFormatter);
export default MessageFormatter;