fix-spend-logs

This commit is contained in:
OrionCodeDev 2026-02-08 21:07:58 +01:00
parent 4f96a3b126
commit af338653ee
3 changed files with 51 additions and 34 deletions

View file

@ -1856,7 +1856,8 @@ async def ui_view_spend_logs( # noqa: PLR0915
raise handle_exception_on_proxy(e)
@lru_cache(maxsize=128)
# 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"],

View file

@ -1,7 +1,9 @@
import { useState } from "react";
import { Drawer, Typography, Descriptions, Card, Tag, Tabs, Alert, Collapse, Radio, Space } from "antd";
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 { formatNumberWithCommas } from "@/utils/dataUtils";
import GuardrailViewer from "../GuardrailViewer/GuardrailViewer";
import { CostBreakdownViewer } from "../CostBreakdownViewer";
@ -45,6 +47,8 @@ export interface LogDetailsDrawerProps {
onOpenSettings?: () => void;
allLogs?: LogEntry[];
onSelectLog?: (log: LogEntry) => void;
accessToken?: string;
startTime?: string;
}
/**
@ -64,6 +68,8 @@ export function LogDetailsDrawer({
onOpenSettings,
allLogs = [],
onSelectLog,
accessToken,
startTime,
}: LogDetailsDrawerProps) {
const [activeTab, setActiveTab] = useState<typeof TAB_REQUEST | typeof TAB_RESPONSE>(TAB_REQUEST);
@ -76,16 +82,35 @@ export function LogDetailsDrawer({
onSelectLog,
});
// 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],
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
});
if (!logEntry) return null;
// Merge lazy-loaded details into the log entry
const detailsData = logDetails.data as any;
const effectiveMessages = detailsData?.messages || logEntry.messages;
const effectiveResponse = detailsData?.response || logEntry.response;
const isLoadingDetails = logDetails.isLoading;
const metadata = logEntry.metadata || {};
const hasError = metadata.status === "failure";
const errorInfo = hasError ? metadata.error_information : null;
// Check if request/response data is present
const hasMessages = checkHasMessages(logEntry.messages);
const hasResponse = checkHasResponse(logEntry.response);
const missingData = !hasMessages && !hasResponse;
// Check if request/response data is present (using lazy-loaded data)
const hasMessages = checkHasMessages(effectiveMessages);
const hasResponse = checkHasResponse(effectiveResponse);
const missingData = !hasMessages && !hasResponse && !isLoadingDetails;
// Guardrail data
const guardrailInfo = metadata?.guardrail_information;
@ -103,7 +128,7 @@ export function LogDetailsDrawer({
const environment = metadata?.user_api_key_team_alias || "default";
const getRawRequest = () => {
return formatData(logEntry.proxy_server_request || logEntry.messages);
return formatData(logEntry.proxy_server_request || effectiveMessages);
};
const getFormattedResponse = () => {
@ -117,7 +142,7 @@ export function LogDetailsDrawer({
},
};
}
return formatData(logEntry.response);
return formatData(effectiveResponse);
};
return (
@ -204,12 +229,19 @@ export function LogDetailsDrawer({
)}
{/* Request/Response JSON - Collapsible */}
<RequestResponseSection
hasResponse={hasResponse}
getRawRequest={getRawRequest}
getFormattedResponse={getFormattedResponse}
logEntry={logEntry}
/>
{isLoadingDetails ? (
<div className="bg-white rounded-lg shadow w-full max-w-full overflow-hidden mb-6 p-8 text-center">
<Spin size="default" />
<div style={{ marginTop: 8, color: "#999" }}>Loading request & response data...</div>
</div>
) : (
<RequestResponseSection
hasResponse={hasResponse}
getRawRequest={getRawRequest}
getFormattedResponse={getFormattedResponse}
logEntry={logEntry}
/>
)}
{/* Guardrail Data - Show only if present */}
{hasGuardrailData && <GuardrailViewer data={guardrailInfo} />}

View file

@ -24,7 +24,6 @@ import { CostBreakdownViewer } from "./CostBreakdownViewer";
import { ErrorViewer } from "./ErrorViewer";
import { useLogFilterLogic } from "./log_filter_logic";
import { getTimeRangeDisplay } from "./logs_utils";
import { prefetchLogDetails } from "./prefetch";
import { RequestResponsePanel } from "./RequestResponsePanel";
import { SessionView } from "./SessionView";
import SpendLogsSettingsModal from "./SpendLogsSettingsModal/SpendLogsSettingsModal";
@ -192,6 +191,8 @@ export default function SpendLogsTable({
: moment().utc().format("YYYY-MM-DD HH:mm:ss");
// Get base response from API
// NOTE: We only fetch the list of logs here (lightweight).
// Log details (messages/response) are fetched on-demand when user clicks a row.
const response = await uiSpendLogsCall(
accessToken,
selectedKeyHash || undefined,
@ -207,25 +208,6 @@ export default function SpendLogsTable({
selectedModel,
);
// Trigger prefetch for all logs
await prefetchLogDetails(response.data, formattedStartTime, accessToken, queryClient);
// Update logs with prefetched data if available
response.data = response.data.map((log: LogEntry) => {
const prefetchedData = queryClient.getQueryData<PrefetchedLog>([
"logDetails",
log.request_id,
formattedStartTime,
]);
if (prefetchedData?.messages && prefetchedData?.response) {
log.messages = prefetchedData.messages;
log.response = prefetchedData.response;
return log;
}
return log;
});
return response;
},
enabled: !!accessToken && !!token && !!userRole && !!userID && activeTab === "request logs",
@ -782,6 +764,8 @@ export default function SpendLogsTable({
onOpenSettings={() => setIsSpendLogsSettingsModalVisible(true)}
allLogs={filteredData}
onSelectLog={handleSelectLog}
accessToken={accessToken}
startTime={moment(startTime).utc().format("YYYY-MM-DD HH:mm:ss")}
/>
</div>
);