Allow viewing keyinfo on request logs (#9568)

* feat(view_logs.tsx): show model id + api base in request logs

easier debugging

* fix(index.tsx): fix length of api base

easier viewing

* build(ui/): initial commit allowing user to click into key from request logs

allows easier debugging of 'what key is this?
This commit is contained in:
Krish Dholakia 2025-03-26 23:11:15 -07:00 committed by GitHub
parent d58fe5a9f9
commit c453a91849
5 changed files with 72 additions and 11 deletions

View file

@ -5,7 +5,7 @@ import { useSearchParams } from "next/navigation";
import { jwtDecode } from "jwt-decode"; import { jwtDecode } from "jwt-decode";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { defaultOrg } from "@/components/common_components/default_org"; import { defaultOrg } from "@/components/common_components/default_org";
import { Team } from "@/components/key_team_helpers/key_list"; import { KeyResponse, Team } from "@/components/key_team_helpers/key_list";
import Navbar from "@/components/navbar"; import Navbar from "@/components/navbar";
import UserDashboard from "@/components/user_dashboard"; import UserDashboard from "@/components/user_dashboard";
import ModelDashboard from "@/components/model_dashboard"; import ModelDashboard from "@/components/model_dashboard";
@ -344,6 +344,7 @@ export default function CreateKeyPage() {
userRole={userRole} userRole={userRole}
token={token} token={token}
accessToken={accessToken} accessToken={accessToken}
allTeams={teams as Team[] ?? []}
/> />
) : ( ) : (
<Usage <Usage

View file

@ -2372,6 +2372,7 @@ export const testConnectionRequest = async (
// ... existing code ... // ... existing code ...
export const keyInfoV1Call = async (accessToken: string, key: string) => { export const keyInfoV1Call = async (accessToken: string, key: string) => {
try { try {
console.log("entering keyInfoV1Call");
let url = proxyBaseUrl ? `${proxyBaseUrl}/key/info` : `/key/info`; let url = proxyBaseUrl ? `${proxyBaseUrl}/key/info` : `/key/info`;
url = `${url}?key=${key}`; // Add key as query parameter url = `${url}?key=${key}`; // Add key as query parameter
@ -2384,13 +2385,16 @@ export const keyInfoV1Call = async (accessToken: string, key: string) => {
// Remove body since this is a GET request // Remove body since this is a GET request
}); });
console.log("response", response);
if (!response.ok) { if (!response.ok) {
const errorData = await response.text(); const errorData = await response.text();
handleError(errorData); handleError(errorData);
throw new Error("Network response was not ok"); message.error("Failed to fetch key info - " + errorData);
} }
const data = await response.json(); const data = await response.json();
console.log("data", data);
return data; return data;
} catch (error) { } catch (error) {
console.error("Failed to fetch key info:", error); console.error("Failed to fetch key info:", error);

View file

@ -6,12 +6,14 @@ import { CountryCell } from "./country_cell";
import { getProviderLogoAndName } from "../provider_info_helpers"; import { getProviderLogoAndName } from "../provider_info_helpers";
import { Tooltip } from "antd"; import { Tooltip } from "antd";
import { TimeCell } from "./time_cell"; import { TimeCell } from "./time_cell";
import { Button } from "@tremor/react";
export type LogEntry = { export type LogEntry = {
request_id: string; request_id: string;
api_key: string; api_key: string;
team_id: string; team_id: string;
model: string; model: string;
model_id: string;
api_base?: string; api_base?: string;
call_type: string; call_type: string;
spend: number; spend: number;
@ -30,6 +32,7 @@ export type LogEntry = {
requester_ip_address?: string; requester_ip_address?: string;
messages: string | any[] | Record<string, any>; messages: string | any[] | Record<string, any>;
response: string | any[] | Record<string, any>; response: string | any[] | Record<string, any>;
onKeyHashClick?: (keyHash: string) => void;
}; };
export const columns: ColumnDef<LogEntry>[] = [ export const columns: ColumnDef<LogEntry>[] = [
@ -140,9 +143,16 @@ export const columns: ColumnDef<LogEntry>[] = [
accessorKey: "metadata.user_api_key", accessorKey: "metadata.user_api_key",
cell: (info: any) => { cell: (info: any) => {
const value = String(info.getValue() || "-"); const value = String(info.getValue() || "-");
const onKeyHashClick = info.row.original.onKeyHashClick;
return ( return (
<Tooltip title={value}> <Tooltip title={value}>
<span className="font-mono max-w-[15ch] truncate block">{value}</span> <span
className="font-mono max-w-[15ch] truncate block cursor-pointer hover:text-blue-600"
onClick={() => onKeyHashClick?.(value)}
>
{value}
</span>
</Tooltip> </Tooltip>
); );
}, },

View file

@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query";
import { useState, useRef, useEffect } from "react"; import { useState, useRef, useEffect } from "react";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { uiSpendLogsCall } from "../networking"; import { uiSpendLogsCall, keyInfoV1Call } from "../networking";
import { DataTable } from "./table"; import { DataTable } from "./table";
import { columns, LogEntry } from "./columns"; import { columns, LogEntry } from "./columns";
import { Row } from "@tanstack/react-table"; import { Row } from "@tanstack/react-table";
@ -12,12 +12,15 @@ import { RequestResponsePanel } from "./columns";
import { ErrorViewer } from './ErrorViewer'; import { ErrorViewer } from './ErrorViewer';
import { internalUserRoles } from "../../utils/roles"; import { internalUserRoles } from "../../utils/roles";
import { ConfigInfoMessage } from './ConfigInfoMessage'; import { ConfigInfoMessage } from './ConfigInfoMessage';
import { Tooltip } from "antd";
import { KeyResponse, Team } from "../key_team_helpers/key_list";
import KeyInfoView from "../key_info_view";
interface SpendLogsTableProps { interface SpendLogsTableProps {
accessToken: string | null; accessToken: string | null;
token: string | null; token: string | null;
userRole: string | null; userRole: string | null;
userID: string | null; userID: string | null;
allTeams: Team[];
} }
interface PaginatedResponse { interface PaginatedResponse {
@ -38,6 +41,7 @@ export default function SpendLogsTable({
token, token,
userRole, userRole,
userID, userID,
allTeams,
}: SpendLogsTableProps) { }: SpendLogsTableProps) {
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [showFilters, setShowFilters] = useState(false); const [showFilters, setShowFilters] = useState(false);
@ -63,14 +67,34 @@ export default function SpendLogsTable({
const [tempKeyHash, setTempKeyHash] = useState(""); const [tempKeyHash, setTempKeyHash] = useState("");
const [selectedTeamId, setSelectedTeamId] = useState(""); const [selectedTeamId, setSelectedTeamId] = useState("");
const [selectedKeyHash, setSelectedKeyHash] = useState(""); const [selectedKeyHash, setSelectedKeyHash] = useState("");
const [selectedKeyInfo, setSelectedKeyInfo] = useState<KeyResponse | null>(null);
const [selectedKeyIdInfoView, setSelectedKeyIdInfoView] = useState<string | null>(null);
const [selectedFilter, setSelectedFilter] = useState("Team ID"); const [selectedFilter, setSelectedFilter] = useState("Team ID");
const [filterByCurrentUser, setFilterByCurrentUser] = useState( const [filterByCurrentUser, setFilterByCurrentUser] = useState(
userRole && internalUserRoles.includes(userRole) userRole && internalUserRoles.includes(userRole)
); );
const [expandedRequestId, setExpandedRequestId] = useState<string | null>(null); const [expandedRequestId, setExpandedRequestId] = useState<string | null>(null);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
useEffect(() => {
const fetchKeyInfo = async () => {
if (selectedKeyIdInfoView && accessToken) {
const keyData = await keyInfoV1Call(accessToken, selectedKeyIdInfoView);
console.log("keyData", keyData);
const keyResponse: KeyResponse = {
...keyData["info"],
"token": selectedKeyIdInfoView,
"api_key": selectedKeyIdInfoView,
};
setSelectedKeyInfo(keyResponse);
}
};
fetchKeyInfo();
}, [selectedKeyIdInfoView, accessToken]);
// Close dropdown when clicking outside // Close dropdown when clicking outside
useEffect(() => { useEffect(() => {
function handleClickOutside(event: MouseEvent) { function handleClickOutside(event: MouseEvent) {
@ -206,7 +230,11 @@ export default function SpendLogsTable({
// No need for additional filtering since we're now handling this in the API call // No need for additional filtering since we're now handling this in the API call
return matchesSearch; return matchesSearch;
}) || [];
}).map(log => ({
...log,
onKeyHashClick: (keyHash: string) => setSelectedKeyIdInfoView(keyHash)
})) || [];
// Add this function to handle manual refresh // Add this function to handle manual refresh
const handleRefresh = () => { const handleRefresh = () => {
@ -242,7 +270,10 @@ export default function SpendLogsTable({
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">
<h1 className="text-xl font-semibold">Request Logs</h1> <h1 className="text-xl font-semibold">Request Logs</h1>
</div> </div>
{selectedKeyInfo && selectedKeyIdInfoView && selectedKeyInfo.api_key === selectedKeyIdInfoView ? (
<KeyInfoView keyId={selectedKeyIdInfoView} keyData={selectedKeyInfo} accessToken={accessToken} userID={userID} userRole={userRole} teams={allTeams} onClose={() => setSelectedKeyIdInfoView(null)} />
) : (
<>
<div className="bg-white rounded-lg shadow"> <div className="bg-white rounded-lg shadow">
<div className="border-b px-6 py-4"> <div className="border-b px-6 py-4">
<div className="flex flex-col md:flex-row items-start md:items-center justify-between space-y-4 md:space-y-0"> <div className="flex flex-col md:flex-row items-start md:items-center justify-between space-y-4 md:space-y-0">
@ -594,6 +625,8 @@ export default function SpendLogsTable({
expandedRequestId={expandedRequestId} expandedRequestId={expandedRequestId}
/> />
</div> </div>
</>
)}
</div> </div>
); );
} }
@ -671,18 +704,25 @@ function RequestViewer({ row }: { row: Row<LogEntry> }) {
<span className="font-medium w-1/3">Model:</span> <span className="font-medium w-1/3">Model:</span>
<span>{row.original.model}</span> <span>{row.original.model}</span>
</div> </div>
<div className="flex">
<span className="font-medium w-1/3">Model ID:</span>
<span>{row.original.model_id}</span>
</div>
<div className="flex"> <div className="flex">
<span className="font-medium w-1/3">Provider:</span> <span className="font-medium w-1/3">Provider:</span>
<span>{row.original.custom_llm_provider || "-"}</span> <span>{row.original.custom_llm_provider || "-"}</span>
</div> </div>
<div className="flex">
<span className="font-medium w-1/3">API Base:</span>
<Tooltip title={row.original.api_base || "-"}>
<span className="max-w-[15ch] truncate block">{row.original.api_base || "-"}</span>
</Tooltip>
</div>
<div className="flex"> <div className="flex">
<span className="font-medium w-1/3">Start Time:</span> <span className="font-medium w-1/3">Start Time:</span>
<span>{row.original.startTime}</span> <span>{row.original.startTime}</span>
</div> </div>
<div className="flex">
<span className="font-medium w-1/3">End Time:</span>
<span>{row.original.endTime}</span>
</div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<div className="flex"> <div className="flex">
@ -712,6 +752,11 @@ function RequestViewer({ row }: { row: Row<LogEntry> }) {
}`}> }`}>
{(row.original.metadata?.status || "Success").toLowerCase() !== "failure" ? "Success" : "Failure"} {(row.original.metadata?.status || "Success").toLowerCase() !== "failure" ? "Success" : "Failure"}
</span> </span>
</div>
<div className="flex">
<span className="font-medium w-1/3">End Time:</span>
<span>{row.original.endTime}</span>
</div> </div>
</div> </div>
</div> </div>

View file

@ -25,6 +25,7 @@ interface DataTableProps<TData, TValue> {
isLoading?: boolean; isLoading?: boolean;
expandedRequestId?: string | null; expandedRequestId?: string | null;
onRowExpand?: (requestId: string | null) => void; onRowExpand?: (requestId: string | null) => void;
setSelectedKeyIdInfoView?: (keyId: string | null) => void;
} }
export function DataTable<TData extends { request_id: string }, TValue>({ export function DataTable<TData extends { request_id: string }, TValue>({