diff --git a/frontend/src/components/LogDetail/index.tsx b/frontend/src/components/LogDetail/index.tsx index 5421672529..b138718ed9 100644 --- a/frontend/src/components/LogDetail/index.tsx +++ b/frontend/src/components/LogDetail/index.tsx @@ -2,6 +2,7 @@ import './LogDetails.styles.scss'; import { Color, Spacing } from '@signozhq/design-tokens'; +import Convert from 'ansi-to-html'; import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd'; import { RadioChangeEvent } from 'antd/lib'; import cx from 'classnames'; @@ -10,8 +11,13 @@ import { LOCALSTORAGE } from 'constants/localStorage'; import ContextView from 'container/LogDetailedView/ContextView/ContextView'; import JSONView from 'container/LogDetailedView/JsonView'; import Overview from 'container/LogDetailedView/Overview'; -import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils'; +import { + aggregateAttributesResourcesToString, + removeEscapeCharacters, + unescapeString, +} from 'container/LogDetailedView/utils'; import { useOptionsMenu } from 'container/OptionsMenu'; +import dompurify from 'dompurify'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useNotifications } from 'hooks/useNotifications'; @@ -28,11 +34,14 @@ import { useMemo, useState } from 'react'; import { useCopyToClipboard } from 'react-use'; import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { DataSource, StringOperators } from 'types/common/queryBuilder'; +import { FORBID_DOM_PURIFY_TAGS } from 'utils/app'; import { VIEW_TYPES, VIEWS } from './constants'; import { LogDetailProps } from './LogDetail.interfaces'; import QueryBuilderSearchWrapper from './QueryBuilderSearchWrapper'; +const convert = new Convert(); + function LogDetail({ log, onClose, @@ -90,6 +99,17 @@ function LogDetail({ } }; + const htmlBody = useMemo( + () => ({ + __html: convert.toHtml( + dompurify.sanitize(unescapeString(log?.body || ''), { + FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS], + }), + ), + }), + [log?.body], + ); + const handleJSONCopy = (): void => { copyToClipboard(LogJsonData); notifications.success({ @@ -127,8 +147,8 @@ function LogDetail({ >
- - {log?.body} + +
 
diff --git a/frontend/src/components/Logs/ListLogView/index.tsx b/frontend/src/components/Logs/ListLogView/index.tsx index 34b3fddd19..ed2627552d 100644 --- a/frontend/src/components/Logs/ListLogView/index.tsx +++ b/frontend/src/components/Logs/ListLogView/index.tsx @@ -6,6 +6,7 @@ import { Typography } from 'antd'; import cx from 'classnames'; import LogDetail from 'components/LogDetail'; import { VIEW_TYPES } from 'components/LogDetail/constants'; +import { unescapeString } from 'container/LogDetailedView/utils'; import { FontSize } from 'container/OptionsMenu/types'; import dayjs from 'dayjs'; import dompurify from 'dompurify'; @@ -56,7 +57,7 @@ function LogGeneralField({ const html = useMemo( () => ({ __html: convert.toHtml( - dompurify.sanitize(fieldValue, { + dompurify.sanitize(unescapeString(fieldValue), { FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS], }), ), diff --git a/frontend/src/components/Logs/RawLogView/index.tsx b/frontend/src/components/Logs/RawLogView/index.tsx index 292d7e029a..2cda9c7247 100644 --- a/frontend/src/components/Logs/RawLogView/index.tsx +++ b/frontend/src/components/Logs/RawLogView/index.tsx @@ -4,6 +4,7 @@ import Convert from 'ansi-to-html'; import { DrawerProps } from 'antd'; import LogDetail from 'components/LogDetail'; import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants'; +import { unescapeString } from 'container/LogDetailedView/utils'; import LogsExplorerContext from 'container/LogsExplorerContext'; import dayjs from 'dayjs'; import dompurify from 'dompurify'; @@ -145,7 +146,9 @@ function RawLogView({ const html = useMemo( () => ({ __html: convert.toHtml( - dompurify.sanitize(text, { FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS] }), + dompurify.sanitize(unescapeString(text), { + FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS], + }), ), }), [text], diff --git a/frontend/src/components/Logs/TableView/useTableView.tsx b/frontend/src/components/Logs/TableView/useTableView.tsx index 3a3ad54e3b..43b4ba2628 100644 --- a/frontend/src/components/Logs/TableView/useTableView.tsx +++ b/frontend/src/components/Logs/TableView/useTableView.tsx @@ -4,6 +4,7 @@ import Convert from 'ansi-to-html'; import { Typography } from 'antd'; import { ColumnsType } from 'antd/es/table'; import cx from 'classnames'; +import { unescapeString } from 'container/LogDetailedView/utils'; import dayjs from 'dayjs'; import dompurify from 'dompurify'; import { useIsDarkMode } from 'hooks/useDarkMode'; @@ -115,7 +116,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => { {}} diff --git a/frontend/src/container/LogDetailedView/TableView/TableViewActions.tsx b/frontend/src/container/LogDetailedView/TableView/TableViewActions.tsx index b239e64d3a..dc0347232d 100644 --- a/frontend/src/container/LogDetailedView/TableView/TableViewActions.tsx +++ b/frontend/src/container/LogDetailedView/TableView/TableViewActions.tsx @@ -1,17 +1,20 @@ import './TableViewActions.styles.scss'; import { Color } from '@signozhq/design-tokens'; +import Convert from 'ansi-to-html'; import { Button, Popover, Spin, Tooltip, Tree } from 'antd'; import GroupByIcon from 'assets/CustomIcons/GroupByIcon'; import cx from 'classnames'; import CopyClipboardHOC from 'components/Logs/CopyClipboardHOC'; import { OPERATORS } from 'constants/queryBuilder'; import ROUTES from 'constants/routes'; +import dompurify from 'dompurify'; import { isEmpty } from 'lodash-es'; import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react'; import { useMemo, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse'; +import { FORBID_DOM_PURIFY_TAGS } from 'utils/app'; import { DataType } from '../TableView'; import { @@ -19,6 +22,7 @@ import { jsonToDataNodes, recursiveParseJSON, removeEscapeCharacters, + unescapeString, } from '../utils'; interface ITableViewActionsProps { @@ -39,6 +43,8 @@ interface ITableViewActionsProps { ) => () => void; } +const convert = new Convert(); + export function TableViewActions( props: ITableViewActionsProps, ): React.ReactElement { @@ -71,22 +77,45 @@ export function TableViewActions( ); } } + const bodyHtml = + record.field === 'body' + ? { + __html: convert.toHtml( + dompurify.sanitize(unescapeString(record.value), { + FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS], + }), + ), + } + : { __html: '' }; const fieldFilterKey = filterKeyForField(fieldData.field); return (
- - - {removeEscapeCharacters(fieldData.value)} - - + {record.field === 'body' ? ( + + + + ) : ( + + + {removeEscapeCharacters(fieldData.value)} + + + )} {!isListViewPanel && ( diff --git a/frontend/src/container/LogDetailedView/utils.tsx b/frontend/src/container/LogDetailedView/utils.tsx index 8418e13893..766bb8b5bf 100644 --- a/frontend/src/container/LogDetailedView/utils.tsx +++ b/frontend/src/container/LogDetailedView/utils.tsx @@ -250,19 +250,37 @@ export const getDataTypes = (value: unknown): DataTypes => { return determineType(value); }; +// now we do not want to render colors everywhere like in tooltip and monaco editor hence we remove such codes to make +// the log line readable export const removeEscapeCharacters = (str: string): string => - str.replace(/\\([ntfr'"\\])/g, (_: string, char: string) => { - const escapeMap: Record = { - n: '\n', - t: '\t', - f: '\f', - r: '\r', - "'": "'", - '"': '"', - '\\': '\\', - }; - return escapeMap[char as keyof typeof escapeMap]; - }); + str + .replace(/\\x1[bB][[0-9;]*m/g, '') + .replace(/\\u001[bB][[0-9;]*m/g, '') + .replace(/\\x[0-9A-Fa-f]{2}/g, '') + .replace(/\\u[0-9A-Fa-f]{4}/g, '') + .replace(/\\[btnfrv0'"\\]/g, ''); + +// we need to remove the escape from the escaped characters as some recievers like file log escape the unicode escape characters. +// example: Log [\u001B[32;1mThis is bright green\u001B[0m] is being sent as [\\u001B[32;1mThis is bright green\\u001B[0m] +// +// so we need to remove this escapes to render the color properly +export const unescapeString = (str: string): string => + str + .replace(/\\n/g, '\n') // Replaces escaped newlines + .replace(/\\r/g, '\r') // Replaces escaped carriage returns + .replace(/\\t/g, '\t') // Replaces escaped tabs + .replace(/\\b/g, '\b') // Replaces escaped backspaces + .replace(/\\f/g, '\f') // Replaces escaped form feeds + .replace(/\\v/g, '\v') // Replaces escaped vertical tabs + .replace(/\\'/g, "'") // Replaces escaped single quotes + .replace(/\\"/g, '"') // Replaces escaped double quotes + .replace(/\\\\/g, '\\') // Replaces escaped backslashes + .replace(/\\x([0-9A-Fa-f]{2})/g, (_, hex) => + String.fromCharCode(parseInt(hex, 16)), + ) // Replaces hexadecimal escape sequences + .replace(/\\u([0-9A-Fa-f]{4})/g, (_, hex) => + String.fromCharCode(parseInt(hex, 16)), + ); // Replaces Unicode escape sequences export function removeExtraSpaces(input: string): string { return input.replace(/\s+/g, ' ').trim();