Skip to content

Commit 8ba60bf

Browse files
authored
(UI + SpendLogs) - Store SpendLogs in UTC Timezone, Fix filtering logs by start/end time (#8190)
* fix request_id field * spend logs store time in UTC * fix ui_view_spend_logs * UI make time filter queries in UTC * fix time filters * fix TimeCellProps * ui use UTC for filtering time
1 parent c0f3100 commit 8ba60bf

File tree

5 files changed

+71
-18
lines changed

5 files changed

+71
-18
lines changed

litellm/proxy/spend_tracking/spend_management_endpoints.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#### SPEND MANAGEMENT #####
22
import collections
33
import os
4-
from datetime import datetime, timedelta
4+
from datetime import datetime, timedelta, timezone
55
from typing import TYPE_CHECKING, Any, List, Optional
66

77
import fastapi
@@ -1688,13 +1688,18 @@ async def ui_view_spend_logs( # noqa: PLR0915
16881688
)
16891689

16901690
try:
1691+
16911692
# Convert the date strings to datetime objects
1692-
start_date_obj = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S")
1693-
end_date_obj = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S")
1693+
start_date_obj = datetime.strptime(start_date, "%Y-%m-%d %H:%M:%S").replace(
1694+
tzinfo=timezone.utc
1695+
)
1696+
end_date_obj = datetime.strptime(end_date, "%Y-%m-%d %H:%M:%S").replace(
1697+
tzinfo=timezone.utc
1698+
)
16941699

16951700
# Convert to ISO format strings for Prisma
1696-
start_date_iso = start_date_obj.isoformat() + "Z" # Add Z to indicate UTC
1697-
end_date_iso = end_date_obj.isoformat() + "Z" # Add Z to indicate UTC
1701+
start_date_iso = start_date_obj.isoformat() # Already in UTC, no need to add Z
1702+
end_date_iso = end_date_obj.isoformat() # Already in UTC, no need to add Z
16981703

16991704
# Build where conditions
17001705
where_conditions: dict[str, Any] = {

litellm/proxy/spend_tracking/spend_tracking_utils.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
22
import secrets
3+
from datetime import datetime
34
from datetime import datetime as dt
5+
from datetime import timezone
46
from typing import Optional, cast
57

68
from pydantic import BaseModel
@@ -153,9 +155,9 @@ def get_logging_payload( # noqa: PLR0915
153155
call_type=call_type or "",
154156
api_key=str(api_key),
155157
cache_hit=str(cache_hit),
156-
startTime=start_time,
157-
endTime=end_time,
158-
completionStartTime=completion_start_time,
158+
startTime=_ensure_datetime_utc(start_time),
159+
endTime=_ensure_datetime_utc(end_time),
160+
completionStartTime=_ensure_datetime_utc(completion_start_time),
159161
model=kwargs.get("model", "") or "",
160162
user=kwargs.get("litellm_params", {})
161163
.get("metadata", {})
@@ -195,6 +197,12 @@ def get_logging_payload( # noqa: PLR0915
195197
raise e
196198

197199

200+
def _ensure_datetime_utc(timestamp: datetime) -> datetime:
201+
"""Helper to ensure datetime is in UTC"""
202+
timestamp = timestamp.astimezone(timezone.utc)
203+
return timestamp
204+
205+
198206
async def get_spend_by_team_and_customer(
199207
start_date: dt,
200208
end_date: dt,

ui/litellm-dashboard/src/components/view_logs/columns.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React from "react";
55
import { CountryCell } from "./country_cell";
66
import { getProviderLogoAndName } from "../provider_info_helpers";
77
import { Tooltip } from "antd";
8+
import { TimeCell } from "./time_cell";
89

910
export type LogEntry = {
1011
request_id: string;
@@ -53,17 +54,17 @@ export const columns: ColumnDef<LogEntry>[] = [
5354
{
5455
header: "Time",
5556
accessorKey: "startTime",
56-
cell: (info: any) => (
57-
<span>{moment(info.getValue()).format("MMM DD HH:mm:ss")}</span>
58-
),
57+
cell: (info: any) => <TimeCell utcTime={info.getValue()} />,
5958
},
6059
{
6160
header: "Request ID",
6261
accessorKey: "request_id",
6362
cell: (info: any) => (
64-
<span className="font-mono text-xs max-w-[100px] truncate block">
65-
{String(info.getValue() || "")}
66-
</span>
63+
<Tooltip title={String(info.getValue() || "")}>
64+
<span className="font-mono text-xs max-w-[100px] truncate block">
65+
{String(info.getValue() || "")}
66+
</span>
67+
</Tooltip>
6768
),
6869
},
6970
{

ui/litellm-dashboard/src/components/view_logs/index.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,11 @@ export default function SpendLogsTable({
105105
};
106106
}
107107

108-
const formattedStartTime = moment(startTime).format("YYYY-MM-DD HH:mm:ss");
108+
// Convert times to UTC before formatting
109+
const formattedStartTime = moment(startTime).utc().format("YYYY-MM-DD HH:mm:ss");
109110
const formattedEndTime = isCustomDate
110-
? moment(endTime).format("YYYY-MM-DD HH:mm:ss")
111-
: moment().format("YYYY-MM-DD HH:mm:ss");
111+
? moment(endTime).utc().format("YYYY-MM-DD HH:mm:ss")
112+
: moment().utc().format("YYYY-MM-DD HH:mm:ss");
112113

113114
return await uiSpendLogsCall(
114115
accessToken,
@@ -176,7 +177,7 @@ export default function SpendLogsTable({
176177
<h1 className="text-xl font-semibold">Request Logs</h1>
177178
</div>
178179

179-
<div className="bg-white rounded-lg shadow overflow-hidden">
180+
<div className="bg-white rounded-lg shadow">
180181
<div className="border-b px-6 py-4">
181182
<div className="flex flex-col md:flex-row items-start md:items-center justify-between space-y-4 md:space-y-0">
182183
<div className="flex flex-wrap items-center gap-3">
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as React from "react";
2+
3+
interface TimeCellProps {
4+
utcTime: string;
5+
}
6+
7+
const getLocalTime = (utcTime: string): string => {
8+
try {
9+
const date = new Date(utcTime);
10+
return date.toLocaleString('en-US', {
11+
year: 'numeric',
12+
month: '2-digit',
13+
day: '2-digit',
14+
hour: '2-digit',
15+
minute: '2-digit',
16+
second: '2-digit',
17+
hour12: true
18+
}).replace(',', '');
19+
} catch (e) {
20+
return "Error converting time";
21+
}
22+
};
23+
24+
export const TimeCell: React.FC<TimeCellProps> = ({ utcTime }) => {
25+
return (
26+
<span style={{
27+
fontFamily: 'monospace',
28+
width: '180px',
29+
display: 'inline-block'
30+
}}>
31+
{getLocalTime(utcTime)}
32+
</span>
33+
);
34+
};
35+
36+
export const getTimeZone = (): string => {
37+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
38+
};

0 commit comments

Comments
 (0)