messageFormatter.tsx 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import {memo} from 'react';
  2. import isObject from 'lodash/isObject';
  3. import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
  4. import {getMeta} from 'sentry/components/events/meta/metaProxy';
  5. import {OnExpandCallback} from 'sentry/components/objectInspector';
  6. import {BreadcrumbTypeDefault, Crumb} from 'sentry/types/breadcrumbs';
  7. import {objectIsEmpty} from 'sentry/utils';
  8. import Format from './format';
  9. interface Props {
  10. breadcrumb: Extract<Crumb, BreadcrumbTypeDefault>;
  11. expandPaths?: string[];
  12. onExpand?: OnExpandCallback;
  13. }
  14. /**
  15. * Attempt to emulate the browser console as much as possible
  16. */
  17. function UnmemoizedMessageFormatter({breadcrumb, expandPaths, onExpand}: Props) {
  18. let args = breadcrumb.data?.arguments;
  19. if (!args) {
  20. // There is a possibility that we don't have arguments as we could be receiving an exception type breadcrumb.
  21. // In these cases we just need the message prop.
  22. // There are cases in which our prop message is an array, we want to force it to become a string
  23. return (
  24. <AnnotatedText
  25. meta={getMeta(breadcrumb, 'message')}
  26. value={breadcrumb.message?.toString() || ''}
  27. />
  28. );
  29. }
  30. // There is a special case where `console.error()` is called with an Error object.
  31. // The SDK uses the Error's `message` property as the breadcrumb message, but we lose the Error type,
  32. // resulting in an empty object in the breadcrumb arguments.
  33. //
  34. // In this special case, we re-create the error object
  35. const isSerializedError =
  36. breadcrumb.message &&
  37. typeof breadcrumb.message === 'string' &&
  38. args.length <= 2 &&
  39. isObject(args[0]);
  40. // Turn this back into an Error object so <Format> can pretty print it
  41. if (isSerializedError && objectIsEmpty(args[0]) && breadcrumb.message) {
  42. // Sometimes message can include stacktrace
  43. const splitMessage = breadcrumb.message.split('\n');
  44. const errorMessagePiece = splitMessage[0].trim();
  45. // Error.prototype.toString() will prepend the error type meaning it will
  46. // not be the same as `message` property. We want message only when
  47. // creating a new Error instance, otherwise the type will repeat.
  48. const errorMessageSplit = errorMessagePiece.split('Error: ');
  49. // Restitch together in case there were other `Error: ` strings in the message
  50. const errorMessage = errorMessageSplit
  51. .splice(errorMessageSplit.length - 1)
  52. .join('Error: ');
  53. const fakeError = new Error(errorMessage);
  54. try {
  55. // Messages generally do not include stack trace due to SDK serialization
  56. fakeError.stack = args.length === 2 ? args[1] : undefined;
  57. // Re-create the error name
  58. if (errorMessageSplit.length > 1) {
  59. fakeError.name = errorMessageSplit[0] + 'Error: ';
  60. }
  61. } catch {
  62. // Some browsers won't allow you to write to error properties
  63. }
  64. args = [fakeError];
  65. }
  66. return <Format expandPaths={expandPaths} onExpand={onExpand} args={args} />;
  67. }
  68. const MessageFormatter = memo(UnmemoizedMessageFormatter);
  69. export default MessageFormatter;