Skip to content

Commit b21a0af

Browse files
dheerajturagabbovenzi
authored andcommitted
Add collapsible failed task logs to prevent React error 185 (apache#54377)
* Add collapsible failed task logs to prevent React error 185 - Recent failed task logs now start collapsed by default - Added "Expand Logs"/"Hide Logs" toggle buttons with translations - Implemented lazy loading - logs only fetch when expanded - Prevents page crashes from large log content rendering - Increased log container max height from 100px to 200px * Apply suggestion from @bbovenzi Co-authored-by: Brent Bovenzi <[email protected]> * Add optional parameter to useLogs hook to truncate logs before rendering, preventing page unresponsiveness when expanding multiple large log previews. This addresses performance issues with tasks that have very large logs (10M+) while maintaining the lazy loading behavior. - Add limit parameter to useLogs hook Props interface - Implement log truncation logic using useMemo for performance - Update TaskLogPreview to use limit: 100 for overview display - Preserve backward compatibility - existing usage without limit unchanged * Pierre's Suggestions --------- Co-authored-by: Brent Bovenzi <[email protected]>
1 parent d3c7b53 commit b21a0af

File tree

3 files changed

+46
-12
lines changed

3 files changed

+46
-12
lines changed

airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
"assetEvent_other": "Created Asset Events"
8585
},
8686
"failedLogs": {
87+
"hideLogs": "Hide Logs",
88+
"showLogs": "Show Logs",
8789
"title": "Recent Failed Task Logs",
8890
"viewFullLogs": "View full logs"
8991
}

airflow-core/src/airflow/ui/src/pages/Dag/Overview/TaskLogPreview.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
import { Box, Flex, Link } from "@chakra-ui/react";
19+
import { Box, Flex, Link, Button } from "@chakra-ui/react";
20+
import { useState } from "react";
2021
import { useTranslation } from "react-i18next";
2122
import { Link as RouterLink } from "react-router-dom";
2223

@@ -36,28 +37,37 @@ export const TaskLogPreview = ({
3637
readonly wrap: boolean;
3738
}) => {
3839
const { t: translate } = useTranslation("dag");
40+
const [isExpanded, setIsExpanded] = useState(false);
41+
3942
const { data, error, isLoading } = useLogs(
4043
{
4144
dagId: taskInstance.dag_id,
45+
limit: 100,
4246
logLevelFilters: ["error", "critical"],
4347
taskInstance,
4448
tryNumber: taskInstance.try_number,
4549
},
4650
{
51+
enabled: isExpanded,
4752
refetchInterval: false,
4853
retry: false,
4954
},
5055
);
5156

5257
return (
5358
<Box borderRadius={4} borderStyle="solid" borderWidth={1} key={taskInstance.id} width="100%">
54-
<Flex alignItems="center" justifyContent="space-between" px={2}>
59+
<Flex alignItems="center" justifyContent="space-between" px={2} py={2}>
5560
<Box>
5661
<StateBadge mr={1} state={taskInstance.state} />
5762
{taskInstance.task_display_name}
5863
<Time datetime={taskInstance.run_after} ml={1} />
5964
</Box>
6065
<Flex gap={1}>
66+
<Button fontSize="sm" onClick={() => setIsExpanded(!isExpanded)} size="sm" variant="ghost">
67+
{isExpanded
68+
? translate("overview.failedLogs.hideLogs")
69+
: translate("overview.failedLogs.showLogs")}
70+
</Button>
6171
<ClearTaskInstanceButton taskInstance={taskInstance} withText={false} />
6272
<Link asChild color="fg.info" fontSize="sm">
6373
<RouterLink to={getTaskInstanceLink(taskInstance)}>
@@ -66,15 +76,17 @@ export const TaskLogPreview = ({
6676
</Link>
6777
</Flex>
6878
</Flex>
69-
<Box maxHeight="100px" overflow="auto">
70-
<TaskLogContent
71-
error={error}
72-
isLoading={isLoading}
73-
logError={error}
74-
parsedLogs={data.parsedLogs ?? []}
75-
wrap={wrap}
76-
/>
77-
</Box>
79+
{isExpanded ? (
80+
<Box borderTopStyle="solid" borderTopWidth={1} maxHeight="200px" overflow="auto">
81+
<TaskLogContent
82+
error={error}
83+
isLoading={isLoading}
84+
logError={error}
85+
parsedLogs={data.parsedLogs ?? []}
86+
wrap={wrap}
87+
/>
88+
</Box>
89+
) : null}
7890
</Box>
7991
);
8092
};

airflow-core/src/airflow/ui/src/queries/useLogs.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { chakra, Box } from "@chakra-ui/react";
2020
import type { UseQueryOptions } from "@tanstack/react-query";
2121
import dayjs from "dayjs";
2222
import type { TFunction } from "i18next";
23+
import { useMemo } from "react";
2324
import { useTranslation } from "react-i18next";
2425
import innerText from "react-innertext";
2526

@@ -34,6 +35,7 @@ type Props = {
3435
accept?: "*/*" | "application/json" | "application/x-ndjson";
3536
dagId: string;
3637
expanded?: boolean;
38+
limit?: number;
3739
logLevelFilters?: Array<string>;
3840
showSource?: boolean;
3941
showTimestamp?: boolean;
@@ -184,6 +186,7 @@ export const useLogs = (
184186
accept = "application/x-ndjson",
185187
dagId,
186188
expanded,
189+
limit,
187190
logLevelFilters,
188191
showSource,
189192
showTimestamp,
@@ -217,8 +220,25 @@ export const useLogs = (
217220
},
218221
);
219222

223+
// Log truncation is performed in the frontend because the backend
224+
// does not support yet pagination / limits on logs reading endpoint
225+
const truncatedData = useMemo(() => {
226+
if (!data?.content || limit === undefined || limit <= 0) {
227+
return data;
228+
}
229+
230+
const streamingContent = parseStreamingLogContent(data);
231+
const truncatedContent =
232+
streamingContent.length > limit ? streamingContent.slice(-limit) : streamingContent;
233+
234+
return {
235+
...data,
236+
content: truncatedContent,
237+
};
238+
}, [data, limit]);
239+
220240
const parsedData = parseLogs({
221-
data: parseStreamingLogContent(data),
241+
data: parseStreamingLogContent(truncatedData),
222242
expanded,
223243
logLevelFilters,
224244
showSource,

0 commit comments

Comments
 (0)