Merge pull request #18366 from BerriAI/litellm_ui_spend_logs_error_code

[Feature] UI - Add Error Code Filtering on UI
This commit is contained in:
yuneng-jiang 2025-12-22 20:46:47 -08:00 • committed by GitHub
commit fa738f217c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 61 additions and 9 deletions

View file

@ -1771,7 +1771,7 @@ async def ui_view_spend_logs( # noqa: PLR0915
if error_code is not None:
metadata_filters.append({
"path": ["error_information", "error_code"],
"equals": error_code,
"equals": f'"{error_code}"',
})
if metadata_filters:
@ -1781,7 +1781,6 @@ async def ui_view_spend_logs( # noqa: PLR0915
where_conditions["AND"] = where_conditions.get("AND", []) + [
{"metadata": filter_cond} for filter_cond in metadata_filters
]
if end_user is not None:
where_conditions["end_user"] = end_user

View file

@ -1895,9 +1895,12 @@ async def test_ui_view_spend_logs_with_error_code(client):
metadata_filter = where_conditions["metadata"]
if metadata_filter.get("path") == ["error_information", "error_code"]:
error_code = metadata_filter.get("equals")
if error_code == "404":
# Handle both string and integer error codes
# The endpoint wraps error_code in quotes, so strip them for comparison
error_code_value = str(error_code).strip('"')
if error_code_value == "404":
return [mock_spend_logs[0]]
elif error_code == "500":
elif error_code_value == "500":
return [mock_spend_logs[1]]
return mock_spend_logs
@ -1907,9 +1910,12 @@ async def test_ui_view_spend_logs_with_error_code(client):
metadata_filter = where_conditions["metadata"]
if metadata_filter.get("path") == ["error_information", "error_code"]:
error_code = metadata_filter.get("equals")
if error_code == "404":
# Handle both string and integer error codes
# The endpoint wraps error_code in quotes, so strip them for comparison
error_code_value = str(error_code).strip('"')
if error_code_value == "404":
return 1
elif error_code == "500":
elif error_code_value == "500":
return 1
return len(mock_spend_logs)
@ -1995,7 +2001,10 @@ async def test_ui_view_spend_logs_with_error_code_and_key_alias(client):
elif metadata_filter.get("path") == ["error_information", "error_code"]:
error_code_filter = metadata_filter.get("equals")
if key_alias_filter == "test-key-1" and error_code_filter == "500":
# Handle both string and integer error codes
# The endpoint wraps error_code in quotes, so strip them for comparison
error_code_value = str(error_code_filter).strip('"')
if key_alias_filter == "test-key-1" and error_code_value == "500":
return [mock_spend_logs[2]] # Only log3 matches both conditions
return mock_spend_logs
@ -2012,7 +2021,10 @@ async def test_ui_view_spend_logs_with_error_code_and_key_alias(client):
elif metadata_filter.get("path") == ["error_information", "error_code"]:
error_code_filter = metadata_filter.get("equals")
if key_alias_filter == "test-key-1" and error_code_filter == "500":
# Handle both string and integer error codes
# The endpoint wraps error_code in quotes, so strip them for comparison
error_code_value = str(error_code_filter).strip('"')
if key_alias_filter == "test-key-1" and error_code_value == "500":
return 1
return len(mock_spend_logs)

View file

@ -129,6 +129,7 @@ const FilterComponent: React.FC<FilterComponentProps> = ({
"Key Alias",
"User ID",
"End User",
"Error Code",
"Key Hash",
"Model",
];

View file

@ -2687,6 +2687,7 @@ export const uiSpendLogsCall = async (
status_filter?: string,
model?: string,
keyAlias?: string,
error_code?: string,
) => {
try {
// Construct base URL
@ -2706,6 +2707,7 @@ export const uiSpendLogsCall = async (
if (status_filter) queryParams.append("status_filter", status_filter);
if (model) queryParams.append("model", model);
if (keyAlias) queryParams.append("key_alias", keyAlias);
if (error_code) queryParams.append("error_code", error_code);
// Append query parameters to URL if any exist
const queryString = queryParams.toString();
if (queryString) {

View file

@ -366,6 +366,24 @@ export default function SpendLogsTable({
setExpandedRequestId(requestId);
};
// Function to extract unique error codes from logs
const extractErrorCodes = (logs: LogEntry[], searchText: string = "") => {
const errorCodes = new Set<string>();
logs.forEach((log) => {
const metadata = log.metadata || {};
if (metadata.status === "failure" && metadata.error_information) {
const errorCode = metadata.error_information.error_code;
if (errorCode && (!searchText || errorCode.toLowerCase().includes(searchText.toLowerCase()))) {
errorCodes.add(errorCode);
}
}
});
return Array.from(errorCodes).map((code) => ({
label: code,
value: code,
}));
};
const logFilterOptions: FilterOption[] = [
{
name: "Team ID",
@ -426,6 +444,14 @@ export default function SpendLogsTable({
return filtered.map((u: string) => ({ label: u, value: u }));
},
},
{
name: "Error Code",
label: "Error Code",
isSearchable: true,
searchFn: async (searchText: string) => {
return extractErrorCodes(logsData.data, searchText);
},
},
{
name: "Key Hash",
label: "Key Hash",

View file

@ -17,6 +17,7 @@ export const FILTER_KEYS = {
END_USER: "End User",
STATUS: "Status",
KEY_ALIAS: "Key Alias",
ERROR_CODE: "Error Code",
} as const;
export type FilterKey = keyof typeof FILTER_KEYS;
@ -53,6 +54,7 @@ export function useLogFilterLogic({
[FILTER_KEYS.END_USER]: "",
[FILTER_KEYS.STATUS]: "",
[FILTER_KEYS.KEY_ALIAS]: "",
[FILTER_KEYS.ERROR_CODE]: "",
}),
[],
);
@ -94,6 +96,7 @@ export function useLogFilterLogic({
filters[FILTER_KEYS.STATUS] || undefined,
filters[FILTER_KEYS.MODEL] || undefined,
filters[FILTER_KEYS.KEY_ALIAS] || undefined,
filters[FILTER_KEYS.ERROR_CODE] || undefined,
);
if (currentTimestamp === lastSearchTimestamp.current && response.data) {
@ -133,7 +136,8 @@ export function useLogFilterLogic({
filters[FILTER_KEYS.KEY_HASH] ||
filters[FILTER_KEYS.REQUEST_ID] ||
filters[FILTER_KEYS.USER_ID] ||
filters[FILTER_KEYS.END_USER]
filters[FILTER_KEYS.END_USER] ||
filters[FILTER_KEYS.ERROR_CODE]
),
[filters],
);
@ -182,6 +186,14 @@ export function useLogFilterLogic({
filteredData = filteredData.filter((log) => log.end_user === filters[FILTER_KEYS.END_USER]);
}
if (filters[FILTER_KEYS.ERROR_CODE]) {
filteredData = filteredData.filter((log) => {
const metadata = log.metadata || {};
const errorInfo = metadata.error_information;
return errorInfo && errorInfo.error_code === filters[FILTER_KEYS.ERROR_CODE];
});
}
return {
data: filteredData,
total: logs.total,