Skip to content

Commit b80f342

Browse files
Aditya SinghAditya Singh
authored andcommitted
fix: add safeguard for large log body entries
1 parent 629e502 commit b80f342

File tree

7 files changed

+107
-113
lines changed

7 files changed

+107
-113
lines changed

frontend/src/components/LogDetail/index.tsx

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import './LogDetails.styles.scss';
33

44
import { Color, Spacing } from '@signozhq/design-tokens';
5-
import Convert from 'ansi-to-html';
65
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
76
import { RadioChangeEvent } from 'antd/lib';
87
import cx from 'classnames';
@@ -16,12 +15,10 @@ import JSONView from 'container/LogDetailedView/JsonView';
1615
import Overview from 'container/LogDetailedView/Overview';
1716
import {
1817
aggregateAttributesResourcesToString,
19-
escapeHtml,
18+
getSanitizedLogBody,
2019
removeEscapeCharacters,
21-
unescapeString,
2220
} from 'container/LogDetailedView/utils';
2321
import { useOptionsMenu } from 'container/OptionsMenu';
24-
import dompurify from 'dompurify';
2522
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
2623
import { useIsDarkMode } from 'hooks/useDarkMode';
2724
import { useNotifications } from 'hooks/useNotifications';
@@ -45,14 +42,11 @@ import { AppState } from 'store/reducers';
4542
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
4643
import { DataSource, StringOperators } from 'types/common/queryBuilder';
4744
import { GlobalReducer } from 'types/reducer/globalTime';
48-
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
4945

5046
import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
5147
import { LogDetailProps } from './LogDetail.interfaces';
5248
import QueryBuilderSearchWrapper from './QueryBuilderSearchWrapper';
5349

54-
const convert = new Convert();
55-
5650
function LogDetail({
5751
log,
5852
onClose,
@@ -118,11 +112,7 @@ function LogDetail({
118112

119113
const htmlBody = useMemo(
120114
() => ({
121-
__html: convert.toHtml(
122-
dompurify.sanitize(unescapeString(escapeHtml(log?.body || '')), {
123-
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
124-
}),
125-
),
115+
__html: getSanitizedLogBody(log?.body || '', { shouldEscapeHtml: true }),
126116
}),
127117
[log?.body],
128118
);

frontend/src/components/Logs/ListLogView/index.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import './ListLogView.styles.scss';
22

33
import { blue } from '@ant-design/colors';
4-
import Convert from 'ansi-to-html';
54
import { Typography } from 'antd';
65
import cx from 'classnames';
76
import LogDetail from 'components/LogDetail';
87
import { VIEW_TYPES } from 'components/LogDetail/constants';
98
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
10-
import { escapeHtml, unescapeString } from 'container/LogDetailedView/utils';
9+
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
1110
import { FontSize } from 'container/OptionsMenu/types';
12-
import dompurify from 'dompurify';
1311
import { useActiveLog } from 'hooks/logs/useActiveLog';
1412
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
1513
import { useIsDarkMode } from 'hooks/useDarkMode';
@@ -20,7 +18,6 @@ import { useCallback, useMemo, useState } from 'react';
2018
// interfaces
2119
import { IField } from 'types/api/logs/fields';
2220
import { ILog } from 'types/api/logs/log';
23-
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
2421

2522
// components
2623
import AddToQueryHOC, { AddToQueryHOCProps } from '../AddToQueryHOC';
@@ -37,8 +34,6 @@ import {
3734
} from './styles';
3835
import { isValidLogField } from './util';
3936

40-
const convert = new Convert();
41-
4237
interface LogFieldProps {
4338
fieldKey: string;
4439
fieldValue: string;
@@ -57,11 +52,7 @@ function LogGeneralField({
5752
}: LogFieldProps): JSX.Element {
5853
const html = useMemo(
5954
() => ({
60-
__html: convert.toHtml(
61-
dompurify.sanitize(unescapeString(escapeHtml(fieldValue)), {
62-
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
63-
}),
64-
),
55+
__html: getSanitizedLogBody(fieldValue, { shouldEscapeHtml: true }),
6556
}),
6657
[fieldValue],
6758
);

frontend/src/components/Logs/RawLogView/index.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import './RawLogView.styles.scss';
22

3-
import Convert from 'ansi-to-html';
43
import { DrawerProps } from 'antd';
54
import LogDetail from 'components/LogDetail';
65
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
76
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
8-
import { escapeHtml, unescapeString } from 'container/LogDetailedView/utils';
7+
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
98
import LogsExplorerContext from 'container/LogsExplorerContext';
10-
import dompurify from 'dompurify';
119
import { useActiveLog } from 'hooks/logs/useActiveLog';
1210
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
1311
// hooks
@@ -23,7 +21,6 @@ import {
2321
useMemo,
2422
useState,
2523
} from 'react';
26-
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
2724

2825
import LogLinesActionButtons from '../LogLinesActionButtons/LogLinesActionButtons';
2926
import LogStateIndicator from '../LogStateIndicator/LogStateIndicator';
@@ -32,8 +29,6 @@ import { getLogIndicatorType } from '../LogStateIndicator/utils';
3229
import { RawLogContent, RawLogViewContainer } from './styles';
3330
import { RawLogViewProps } from './types';
3431

35-
const convert = new Convert();
36-
3732
function RawLogView({
3833
isActiveLog,
3934
isReadOnly,
@@ -176,11 +171,7 @@ function RawLogView({
176171

177172
const html = useMemo(
178173
() => ({
179-
__html: convert.toHtml(
180-
dompurify.sanitize(unescapeString(escapeHtml(text)), {
181-
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
182-
}),
183-
),
174+
__html: getSanitizedLogBody(text, { shouldEscapeHtml: true }),
184175
}),
185176
[text],
186177
);

frontend/src/components/Logs/TableView/useTableView.tsx

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import './useTableView.styles.scss';
22

3-
import Convert from 'ansi-to-html';
43
import { Typography } from 'antd';
54
import { ColumnsType } from 'antd/es/table';
65
import cx from 'classnames';
76
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
8-
import { unescapeString } from 'container/LogDetailedView/utils';
9-
import dompurify from 'dompurify';
7+
import { getSanitizedLogBody } from 'container/LogDetailedView/utils';
108
import { useIsDarkMode } from 'hooks/useDarkMode';
119
import { FlatLogData } from 'lib/logs/flatLogData';
1210
import { useTimezone } from 'providers/Timezone';
1311
import { useMemo } from 'react';
14-
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
1512

1613
import LogStateIndicator from '../LogStateIndicator/LogStateIndicator';
1714
import { getLogIndicatorTypeForTable } from '../LogStateIndicator/utils';
@@ -27,8 +24,6 @@ import {
2724
UseTableViewResult,
2825
} from './types';
2926

30-
const convert = new Convert();
31-
3227
export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
3328
const {
3429
logs,
@@ -149,11 +144,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
149144
children: (
150145
<TableBodyContent
151146
dangerouslySetInnerHTML={{
152-
__html: convert.toHtml(
153-
dompurify.sanitize(unescapeString(field as string), {
154-
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
155-
}),
156-
),
147+
__html: getSanitizedLogBody(field as string),
157148
}}
158149
fontSize={fontSize}
159150
linesPerRow={linesPerRow}

frontend/src/container/LogDetailedView/TableView/TableViewActions.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import './TableViewActions.styles.scss';
33

44
import { Color } from '@signozhq/design-tokens';
5-
import Convert from 'ansi-to-html';
65
import { Button, Popover, Spin, Tooltip, Tree } from 'antd';
76
import GroupByIcon from 'assets/CustomIcons/GroupByIcon';
87
import cx from 'classnames';
@@ -11,22 +10,19 @@ import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
1110
import { OPERATORS } from 'constants/queryBuilder';
1211
import ROUTES from 'constants/routes';
1312
import { RESTRICTED_SELECTED_FIELDS } from 'container/LogsFilters/config';
14-
import dompurify from 'dompurify';
1513
import { ArrowDownToDot, ArrowUpFromDot, Ellipsis } from 'lucide-react';
1614
import { useTimezone } from 'providers/Timezone';
1715
import React, { useCallback, useMemo, useState } from 'react';
1816
import { useLocation } from 'react-router-dom';
1917
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
20-
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
2118

2219
import { DataType } from '../TableView';
2320
import {
24-
escapeHtml,
2521
filterKeyForField,
2622
getFieldAttributes,
23+
getSanitizedLogBody,
2724
parseFieldValue,
2825
removeEscapeCharacters,
29-
unescapeString,
3026
} from '../utils';
3127
import useAsyncJSONProcessing from './useAsyncJSONProcessing';
3228

@@ -49,8 +45,6 @@ interface ITableViewActionsProps {
4945
) => () => void;
5046
}
5147

52-
const convert = new Convert();
53-
5448
// Memoized Tree Component
5549
const MemoizedTree = React.memo<{ treeData: any[] }>(({ treeData }) => (
5650
<Tree
@@ -69,6 +63,7 @@ const BodyContent: React.FC<{
6963
record: DataType;
7064
bodyHtml: { __html: string };
7165
}> = React.memo(({ fieldData, record, bodyHtml }) => {
66+
console.log('bodyHtml', bodyHtml);
7267
const { isLoading, treeData, error } = useAsyncJSONProcessing(
7368
fieldData.value,
7469
record.field === 'body',
@@ -144,11 +139,7 @@ export default function TableViewActions(
144139
if (record.field !== 'body') return { __html: '' };
145140

146141
return {
147-
__html: convert.toHtml(
148-
dompurify.sanitize(unescapeString(escapeHtml(record.value)), {
149-
FORBID_TAGS: [...FORBID_DOM_PURIFY_TAGS],
150-
}),
151-
),
142+
__html: getSanitizedLogBody(record.value, { shouldEscapeHtml: true }),
152143
};
153144
}, [record.field, record.value]);
154145

frontend/src/container/LogDetailedView/TableView/useAsyncJSONProcessing.ts

Lines changed: 74 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { useEffect, useRef, useState } from 'react';
44

55
import { jsonToDataNodes, recursiveParseJSON } from '../utils';
66

7+
const MAX_BODY_BYTES = 100 * 1024; // 100 KB
8+
79
// Hook for async JSON processing
810
const useAsyncJSONProcessing = (
911
value: string,
@@ -27,68 +29,84 @@ const useAsyncJSONProcessing = (
2729

2830
// eslint-disable-next-line sonarjs/cognitive-complexity
2931
useEffect((): (() => void) => {
30-
if (!shouldProcess || processingRef.current) {
31-
return (): void => {};
32-
}
32+
try {
33+
if (!shouldProcess || processingRef.current) {
34+
return (): void => {};
35+
}
36+
37+
// Check if the JSON is too large
38+
const json = JSON.stringify(value);
39+
const byteSize = new Blob([json]).size;
3340

34-
processingRef.current = true;
35-
setJsonState({ isLoading: true, treeData: null, error: null });
41+
if (byteSize > MAX_BODY_BYTES) {
42+
setJsonState({ isLoading: false, treeData: null, error: null });
43+
return (): void => {};
44+
}
45+
46+
processingRef.current = true;
47+
setJsonState({ isLoading: true, treeData: null, error: null });
3648

37-
// Option 1: Using setTimeout for non-blocking processing
38-
const processAsync = (): void => {
39-
setTimeout(() => {
40-
try {
41-
const parsedBody = recursiveParseJSON(value);
42-
if (!isEmpty(parsedBody)) {
43-
const treeData = jsonToDataNodes(parsedBody);
44-
setJsonState({ isLoading: false, treeData, error: null });
45-
} else {
46-
setJsonState({ isLoading: false, treeData: null, error: null });
49+
// Option 1: Using setTimeout for non-blocking processing
50+
const processAsync = (): void => {
51+
setTimeout(() => {
52+
try {
53+
const parsedBody = recursiveParseJSON(value);
54+
if (!isEmpty(parsedBody)) {
55+
const treeData = jsonToDataNodes(parsedBody);
56+
setJsonState({ isLoading: false, treeData, error: null });
57+
} else {
58+
setJsonState({ isLoading: false, treeData: null, error: null });
59+
}
60+
} catch (error) {
61+
setJsonState({
62+
isLoading: false,
63+
treeData: null,
64+
error: error instanceof Error ? error.message : 'Parsing failed',
65+
});
66+
} finally {
67+
processingRef.current = false;
4768
}
48-
} catch (error) {
49-
setJsonState({
50-
isLoading: false,
51-
treeData: null,
52-
error: error instanceof Error ? error.message : 'Parsing failed',
53-
});
54-
} finally {
55-
processingRef.current = false;
56-
}
57-
}, 0);
58-
};
69+
}, 0);
70+
};
5971

60-
// Option 2: Using requestIdleCallback for better performance
61-
const processWithIdleCallback = (): void => {
62-
if ('requestIdleCallback' in window) {
63-
requestIdleCallback(
64-
// eslint-disable-next-line sonarjs/no-identical-functions
65-
(): void => {
66-
try {
67-
const parsedBody = recursiveParseJSON(value);
68-
if (!isEmpty(parsedBody)) {
69-
const treeData = jsonToDataNodes(parsedBody);
70-
setJsonState({ isLoading: false, treeData, error: null });
71-
} else {
72-
setJsonState({ isLoading: false, treeData: null, error: null });
72+
// Option 2: Using requestIdleCallback for better performance
73+
const processWithIdleCallback = (): void => {
74+
if ('requestIdleCallback' in window) {
75+
requestIdleCallback(
76+
// eslint-disable-next-line sonarjs/no-identical-functions
77+
(): void => {
78+
try {
79+
const parsedBody = recursiveParseJSON(value);
80+
if (!isEmpty(parsedBody)) {
81+
const treeData = jsonToDataNodes(parsedBody);
82+
setJsonState({ isLoading: false, treeData, error: null });
83+
} else {
84+
setJsonState({ isLoading: false, treeData: null, error: null });
85+
}
86+
} catch (error) {
87+
setJsonState({
88+
isLoading: false,
89+
treeData: null,
90+
error: error instanceof Error ? error.message : 'Parsing failed',
91+
});
92+
} finally {
93+
processingRef.current = false;
7394
}
74-
} catch (error) {
75-
setJsonState({
76-
isLoading: false,
77-
treeData: null,
78-
error: error instanceof Error ? error.message : 'Parsing failed',
79-
});
80-
} finally {
81-
processingRef.current = false;
82-
}
83-
},
84-
{ timeout: 1000 },
85-
);
86-
} else {
87-
processAsync();
88-
}
89-
};
95+
},
96+
{ timeout: 1000 },
97+
);
98+
} else {
99+
processAsync();
100+
}
101+
};
90102

91-
processWithIdleCallback();
103+
processWithIdleCallback();
104+
} catch (error) {
105+
console.error('Error processing JSON', error);
106+
setJsonState({ isLoading: false, treeData: null, error: 'Parsing failed' });
107+
} finally {
108+
processingRef.current = false;
109+
}
92110

93111
// Cleanup function
94112
return (): void => {

0 commit comments

Comments
 (0)