messageFormatter.tsx 3.0 KB

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