|
@@ -1,5 +1,6 @@
|
|
|
import {Fragment} from 'react';
|
|
|
import styled from '@emotion/styled';
|
|
|
+import isObject from 'lodash/isObject';
|
|
|
import {sprintf, vsprintf} from 'sprintf-js';
|
|
|
|
|
|
import DateTime from 'sentry/components/dateTime';
|
|
@@ -12,6 +13,7 @@ import Tooltip from 'sentry/components/tooltip';
|
|
|
import {IconClose, IconWarning} from 'sentry/icons';
|
|
|
import space from 'sentry/styles/space';
|
|
|
import {BreadcrumbTypeDefault} from 'sentry/types/breadcrumbs';
|
|
|
+import {objectIsEmpty} from 'sentry/utils';
|
|
|
|
|
|
interface MessageFormatterProps {
|
|
|
breadcrumb: BreadcrumbTypeDefault;
|
|
@@ -35,7 +37,9 @@ function renderString(arg: string | number | boolean | Object) {
|
|
|
/**
|
|
|
* Attempt to emulate the browser console as much as possible
|
|
|
*/
|
|
|
-function MessageFormatter({breadcrumb}: MessageFormatterProps) {
|
|
|
+export function MessageFormatter({breadcrumb}: MessageFormatterProps) {
|
|
|
+ let logMessage = '';
|
|
|
+
|
|
|
// Browser's console formatter only works on the first arg
|
|
|
const [message, ...args] = breadcrumb.data?.arguments;
|
|
|
|
|
@@ -45,35 +49,57 @@ function MessageFormatter({breadcrumb}: MessageFormatterProps) {
|
|
|
? sprintf.parse(message).filter(parsed => Array.isArray(parsed))
|
|
|
: [];
|
|
|
|
|
|
- // TODO `%c` is console specific, it applies colors to messages
|
|
|
- // for now we are stripping it as this is potentially risky to implement due to xss
|
|
|
- const consoleColorPlaceholderIndexes = placeholders
|
|
|
- .filter(([placeholder]) => placeholder === '%c')
|
|
|
- .map((_, i) => i);
|
|
|
-
|
|
|
- // Retrieve message formatting args
|
|
|
- const messageArgs = args.slice(0, placeholders.length);
|
|
|
-
|
|
|
- // Filter out args that were for %c
|
|
|
- for (const colorIndex of consoleColorPlaceholderIndexes) {
|
|
|
- messageArgs.splice(colorIndex, 1);
|
|
|
+ // Placeholders can only occur in the first argument and only if it is a string.
|
|
|
+ // We can skip the below code and avoid using `sprintf` if there are no placeholders.
|
|
|
+ if (placeholders.length) {
|
|
|
+ // TODO `%c` is console specific, it applies colors to messages
|
|
|
+ // for now we are stripping it as this is potentially risky to implement due to xss
|
|
|
+ const consoleColorPlaceholderIndexes = placeholders
|
|
|
+ .filter(([placeholder]) => placeholder === '%c')
|
|
|
+ .map((_, i) => i);
|
|
|
+
|
|
|
+ // Retrieve message formatting args
|
|
|
+ const messageArgs = args.slice(0, placeholders.length);
|
|
|
+
|
|
|
+ // Filter out args that were for %c
|
|
|
+ for (const colorIndex of consoleColorPlaceholderIndexes) {
|
|
|
+ messageArgs.splice(colorIndex, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Attempt to stringify the rest of the args
|
|
|
+ const restArgs = args.slice(placeholders.length).map(renderString);
|
|
|
+
|
|
|
+ const formattedMessage = isMessageString
|
|
|
+ ? vsprintf(message.replaceAll('%c', ''), messageArgs)
|
|
|
+ : renderString(message);
|
|
|
+
|
|
|
+ logMessage = [formattedMessage, ...restArgs].join(' ').trim();
|
|
|
+ } else if (
|
|
|
+ breadcrumb.data?.arguments.length === 1 &&
|
|
|
+ isObject(message) &&
|
|
|
+ objectIsEmpty(message)
|
|
|
+ ) {
|
|
|
+ // 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 case, we
|
|
|
+ // only want to use `breadcrumb.message`.
|
|
|
+ logMessage = breadcrumb.message || JSON.stringify(message);
|
|
|
+ } else {
|
|
|
+ // If the string `[object Object]` is found in message, it means the SDK attempted to stringify an object,
|
|
|
+ // but the actual object should be captured in the arguments.
|
|
|
+ //
|
|
|
+ // Likewise if arrays are found e.g. [test,test] the SDK will serialize it to 'test, test'.
|
|
|
+ //
|
|
|
+ // In those cases, we'll want to use our pretty print in every argument that was passed to the logger instead of using
|
|
|
+ // the SDK's serialization.
|
|
|
+ const argValues = breadcrumb.data?.arguments.map(renderString);
|
|
|
+
|
|
|
+ logMessage = argValues.join(' ').trim();
|
|
|
}
|
|
|
|
|
|
- // Attempt to stringify the rest of the args
|
|
|
- const restArgs = args.slice(placeholders.length).map(renderString);
|
|
|
-
|
|
|
- const formattedMessage = isMessageString
|
|
|
- ? vsprintf(message.replaceAll('%c', ''), messageArgs)
|
|
|
- : renderString(message);
|
|
|
-
|
|
|
// TODO(replays): Add better support for AnnotatedText (e.g. we use message
|
|
|
// args from breadcrumb.data.arguments and not breadcrumb.message directly)
|
|
|
- return (
|
|
|
- <AnnotatedText
|
|
|
- meta={getMeta(breadcrumb, 'message')}
|
|
|
- value={[formattedMessage, ...restArgs].join(' ')}
|
|
|
- />
|
|
|
- );
|
|
|
+ return <AnnotatedText meta={getMeta(breadcrumb, 'message')} value={logMessage} />;
|
|
|
}
|
|
|
|
|
|
interface ConsoleMessageProps extends MessageFormatterProps {
|