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();