Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
"assetEvent_other": "Created Asset Events"
},
"failedLogs": {
"hideLogs": "Hide Logs",
"showLogs": "Show Logs",
"title": "Recent Failed Task Logs",
"viewFullLogs": "View full logs"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
import { Box, Flex, Link } from "@chakra-ui/react";
import { Box, Flex, Link, Button } from "@chakra-ui/react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link as RouterLink } from "react-router-dom";

Expand All @@ -36,28 +37,37 @@ export const TaskLogPreview = ({
readonly wrap: boolean;
}) => {
const { t: translate } = useTranslation("dag");
const [isExpanded, setIsExpanded] = useState(false);

const { data, error, isLoading } = useLogs(
{
dagId: taskInstance.dag_id,
limit: 100,
logLevelFilters: ["error", "critical"],
taskInstance,
tryNumber: taskInstance.try_number,
},
{
enabled: isExpanded,
refetchInterval: false,
retry: false,
},
);

return (
<Box borderRadius={4} borderStyle="solid" borderWidth={1} key={taskInstance.id} width="100%">
<Flex alignItems="center" justifyContent="space-between" px={2}>
<Flex alignItems="center" justifyContent="space-between" px={2} py={2}>
<Box>
<StateBadge mr={1} state={taskInstance.state} />
{taskInstance.task_display_name}
<Time datetime={taskInstance.run_after} ml={1} />
</Box>
<Flex gap={1}>
<Button fontSize="sm" onClick={() => setIsExpanded(!isExpanded)} size="sm" variant="ghost">
{isExpanded
? translate("overview.failedLogs.hideLogs")
: translate("overview.failedLogs.showLogs")}
</Button>
<ClearTaskInstanceButton taskInstance={taskInstance} withText={false} />
<Link asChild color="fg.info" fontSize="sm">
<RouterLink to={getTaskInstanceLink(taskInstance)}>
Expand All @@ -66,15 +76,17 @@ export const TaskLogPreview = ({
</Link>
</Flex>
</Flex>
<Box maxHeight="100px" overflow="auto">
<TaskLogContent
error={error}
isLoading={isLoading}
logError={error}
parsedLogs={data.parsedLogs ?? []}
wrap={wrap}
/>
</Box>
{isExpanded ? (
<Box borderTopStyle="solid" borderTopWidth={1} maxHeight="200px" overflow="auto">
<TaskLogContent
error={error}
isLoading={isLoading}
logError={error}
parsedLogs={data.parsedLogs ?? []}
wrap={wrap}
/>
</Box>
) : null}
</Box>
);
};
22 changes: 21 additions & 1 deletion airflow-core/src/airflow/ui/src/queries/useLogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { chakra, Box } from "@chakra-ui/react";
import type { UseQueryOptions } from "@tanstack/react-query";
import dayjs from "dayjs";
import type { TFunction } from "i18next";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import innerText from "react-innertext";

Expand All @@ -34,6 +35,7 @@ type Props = {
accept?: "*/*" | "application/json" | "application/x-ndjson";
dagId: string;
expanded?: boolean;
limit?: number;
logLevelFilters?: Array<string>;
showSource?: boolean;
showTimestamp?: boolean;
Expand Down Expand Up @@ -184,6 +186,7 @@ export const useLogs = (
accept = "application/x-ndjson",
dagId,
expanded,
limit,
logLevelFilters,
showSource,
showTimestamp,
Expand Down Expand Up @@ -217,8 +220,25 @@ export const useLogs = (
},
);

// Log truncation is performed in the frontend because the backend
// does not support yet pagination / limits on logs reading endpoint
const truncatedData = useMemo(() => {
if (!data?.content || limit === undefined || limit <= 0) {
return data;
}

const streamingContent = parseStreamingLogContent(data);
const truncatedContent =
streamingContent.length > limit ? streamingContent.slice(-limit) : streamingContent;

return {
...data,
content: truncatedContent,
};
}, [data, limit]);

const parsedData = parseLogs({
data: parseStreamingLogContent(data),
data: parseStreamingLogContent(truncatedData),
expanded,
logLevelFilters,
showSource,
Expand Down