diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index 2201324aef4..82ce3039eac 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -1855,8 +1855,6 @@ async def ui_view_spend_logs( # noqa: PLR0915 raise handle_exception_on_proxy(e) -# NOTE: @lru_cache was removed here because it does not work correctly with async functions. -# It caches the coroutine object, not the actual result, which can cause errors and memory issues. @router.get( "/spend/logs/ui/{request_id}", tags=["Budget & Spend Tracking"], diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/logDetails/useLogDetails.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/logDetails/useLogDetails.ts new file mode 100644 index 00000000000..6c0f95d5995 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/logDetails/useLogDetails.ts @@ -0,0 +1,30 @@ +import { useQuery } from "@tanstack/react-query"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { uiSpendLogDetailsCall } from "@/components/networking"; + +/** + * Hook to lazy-load log details (messages/response) for a specific log entry. + * Fetches data on-demand when the drawer is open, instead of prefetching all logs. + * + * @param requestId - The request_id of the log entry + * @param startTime - The formatted start time for the query + * @param enabled - Whether the query should be enabled (e.g., drawer is open) + */ +export const useLogDetails = ( + requestId: string | undefined, + startTime: string | undefined, + enabled: boolean, +) => { + const { accessToken } = useAuthorized(); + + return useQuery({ + queryKey: ["logDetails", requestId, startTime, accessToken], + queryFn: async () => { + if (!accessToken || !requestId || !startTime) return null; + return await uiSpendLogDetailsCall(accessToken, requestId, startTime); + }, + enabled: enabled && !!accessToken && !!requestId && !!startTime, + staleTime: 10 * 60 * 1000, // 10 minutes + gcTime: 10 * 60 * 1000, // 10 minutes + }); +}; diff --git a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx index bd30eb9ba8b..12e929dd7c9 100644 --- a/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx @@ -1,9 +1,8 @@ import { useState } from "react"; -import { useQuery } from "@tanstack/react-query"; import { Drawer, Typography, Descriptions, Card, Tag, Tabs, Alert, Collapse, Radio, Space, Spin } from "antd"; import moment from "moment"; import { LogEntry } from "../columns"; -import { uiSpendLogDetailsCall } from "../../networking"; +import { useLogDetails } from "@/app/(dashboard)/hooks/logDetails/useLogDetails"; import { formatNumberWithCommas } from "@/utils/dataUtils"; import GuardrailViewer from "../GuardrailViewer/GuardrailViewer"; import { CostBreakdownViewer } from "../CostBreakdownViewer"; @@ -47,7 +46,6 @@ export interface LogDetailsDrawerProps { onOpenSettings?: () => void; allLogs?: LogEntry[]; onSelectLog?: (log: LogEntry) => void; - accessToken?: string; startTime?: string; } @@ -68,7 +66,6 @@ export function LogDetailsDrawer({ onOpenSettings, allLogs = [], onSelectLog, - accessToken, startTime, }: LogDetailsDrawerProps) { const [activeTab, setActiveTab] = useState(TAB_REQUEST); @@ -83,24 +80,14 @@ export function LogDetailsDrawer({ }); // Lazy-load log details (messages/response) only when drawer is open - // This fetches data for a single log on-demand instead of prefetching all 50 - const logDetails = useQuery({ - queryKey: ["logDetails", logEntry?.request_id, startTime, accessToken], - queryFn: async () => { - if (!accessToken || !logEntry?.request_id || !startTime) return null; - return await uiSpendLogDetailsCall(accessToken, logEntry.request_id, startTime); - }, - enabled: open && !!accessToken && !!logEntry?.request_id && !!startTime, - staleTime: 10 * 60 * 1000, // 10 minutes - gcTime: 10 * 60 * 1000, // 10 minutes - }); + const logDetails = useLogDetails(logEntry?.request_id, startTime, open && !!logEntry?.request_id); if (!logEntry) return null; - // Merge lazy-loaded details into the log entry + // Use lazy-loaded details data directly const detailsData = logDetails.data as any; - const effectiveMessages = detailsData?.messages || logEntry.messages; - const effectiveResponse = detailsData?.response || logEntry.response; + const effectiveMessages = detailsData?.messages; + const effectiveResponse = detailsData?.response; const isLoadingDetails = logDetails.isLoading; const metadata = logEntry.metadata || {}; diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx index 8389ed5f16c..74e6ea52e56 100644 --- a/ui/litellm-dashboard/src/components/view_logs/index.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx @@ -72,7 +72,7 @@ export default function SpendLogsTable({ const quickSelectRef = useRef(null); // New state variables for Start and End Time - const [startTime, setStartTime] = useState(moment().subtract(4, "hours").format("YYYY-MM-DDTHH:mm")); + const [startTime, setStartTime] = useState(moment().subtract(24, "hours").format("YYYY-MM-DDTHH:mm")); const [endTime, setEndTime] = useState(moment().format("YYYY-MM-DDTHH:mm")); const [isCustomDate, setIsCustomDate] = useState(false); @@ -107,7 +107,7 @@ export default function SpendLogsTable({ }, [isLiveTail]); const [selectedTimeInterval, setSelectedTimeInterval] = useState<{ value: number; unit: string }>({ - value: 4, + value: 24, unit: "hours", }); @@ -764,7 +764,6 @@ export default function SpendLogsTable({ onOpenSettings={() => setIsSpendLogsSettingsModalVisible(true)} allLogs={filteredData} onSelectLog={handleSelectLog} - accessToken={accessToken} startTime={moment(startTime).utc().format("YYYY-MM-DD HH:mm:ss")} />