mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix-spend-logs
This commit is contained in:
parent
4f96a3b126
commit
af338653ee
3 changed files with 51 additions and 34 deletions
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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} />}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue