feat(spend-logs): add lifecycle timestamps - #37361
Conversation
Greptile SummaryThe PR adds database-managed lifecycle timestamps to spend logs and exposes them through the API models.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously reported schema synchronization issue is fixed at current HEAD.
|
| Filename | Overview |
|---|---|
| litellm-proxy-extras/litellm_proxy_extras/migrations/20260818000000_add_spend_log_timestamps/migration.sql | Adds non-null lifecycle timestamp columns with database defaults. |
| schema.prisma | The previous omission is fixed; the root SpendLogs model now matches both schema copies. |
| litellm/proxy/schema.prisma | Adds matching lifecycle fields, including Prisma-managed updated-at behavior. |
| litellm-proxy-extras/litellm_proxy_extras/schema.prisma | Keeps the migration-generation schema synchronized with the root and proxy schemas. |
| litellm/models/spend_logs.py | Exposes optional database lifecycle timestamps in the spend-log API model. |
| tests/test_litellm/models/test_models.py | Covers absent timestamps and parsing populated datetime values. |
| ui/litellm-dashboard/src/lib/http/schema.d.ts | Updates generated frontend API types with the two optional timestamp fields. |
Reviews (3): Last reviewed commit: "chore(ui): regenerate dashboard API type..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
@greptileai Please re-review the latest commit; the root schema now matches both runtime copies and schema-sync CI passes |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6acf970. Configure here.
mateo-berri
left a comment
There was a problem hiding this comment.
LGTM. Thanks for the contribution!
0f19b5b
into
BerriAI:litellm_internal_staging
TLDR
Problem this solves:
How it solves it:
created_atandupdated_atcolumnsUser Flow
Before: a data engineer exporting spend logs into a warehouse only sees request-side timestamps, so their incremental job cannot tell which rows were written since its last run
idstartTimeandendTimeonly, nothing says when the gateway wrote it, so a request logged late lands outside the window the job already exported and the job has to re-export overlapping windows to catch itAfter: the same export carries
created_atandupdated_at, so the incremental job checkpoints on when rows were writtenidcreated_atandupdated_atISO timestamps set when the gateway wrote it, so the job selects rows withcreated_atafter its last checkpoint and stops re-exporting overlapping windowsRelevant issues
Linear ticket
Resolves LIT-5819
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Shared setup for both sides: a proxy booted from a fresh worktree at the named commit, against a fresh Postgres database, default migration mode (
prisma migrate deploy), real OpenAI calls. Config:Every chat request below is
curl -sS -X POST http://localhost:$PORT/v1/chat/completions -H "Authorization: Bearer sk-qa-1234" -H "Content-Type: application/json" -d '{"model":"gpt-5.4-nano","messages":[{"role":"user","content":"Say hi in one word"}]}'. Spend-log rows are shown withmessages,response,metadata, andproxy_server_requesttrimmedBefore (d03ef8b)
Case 1, fresh deployment
Chat request: HTTP 200,
"id": "chatcmpl-EEgahJKQoiuQdhvuDgoxxCh9EKDX4", 15 total tokenscurl -sS "http://localhost:56460/spend/logs?request_id=chatcmpl-EEgahJKQoiuQdhvuDgoxxCh9EKDX4" -H "Authorization: Bearer sk-qa-1234"[{"request_id": "chatcmpl-EEgahJKQoiuQdhvuDgoxxCh9EKDX4", "call_type": "acompletion", "api_key": "litellm_proxy_master_key", "spend": 7.200000000000001e-06, "total_tokens": 15, "prompt_tokens": 11, "completion_tokens": 4, "startTime": "2026-08-19T19:50:18.464000Z", "endTime": "2026-08-19T19:50:19.719000Z", "request_duration_ms": 1254, "completionStartTime": "2026-08-19T19:50:19.718000Z", "model": "openai/gpt-5.4-nano", "model_id": "df6a8a76a5a7f3b6eb5ac1d393b82a1266c52b78ee2da9692b8affe6d1f6bb9c", "model_group": "gpt-5.4-nano", "custom_llm_provider": "openai", "api_base": "https://api.openai.com/v1/", "user": "default_user_id", "cache_hit": "None", "cache_key": "Cache OFF", "request_tags": ["User-Agent: curl", "User-Agent: curl/8.7.1"], "team_id": "", "organization_id": "", "end_user": "", "requester_ip_address": "127.0.0.1", "session_id": "8cdbef08-c498-4d3b-930e-f4963abf5f20", "status": "success", "mcp_namespaced_tool_name": null, "agent_id": null}]No
created_atorupdated_atkey on the rowcurl -sS "http://localhost:56460/spend/logs?start_date=2026-08-19&end_date=2026-08-20&summarize=false" -H "Authorization: Bearer sk-qa-1234": HTTP 200, 1 row, same shape as step 2, nocreated_atorupdated_atCase 2, existing deployment upgraded
"id": "chatcmpl-EEgbvxmg1r3xleeBksJxIxJIhRiyP"curl -sS "http://localhost:56460/spend/logs?start_date=2026-08-19&end_date=2026-08-20&summarize=false" -H "Authorization: Bearer sk-qa-1234": HTTP 200, 2 rows (chatcmpl-EEgbvxmg1r3xleeBksJxIxJIhRiyPwithstartTime2026-08-19T19:51:34.840000Z,chatcmpl-EEgahJKQoiuQdhvuDgoxxCh9EKDX4withstartTime2026-08-19T19:50:18.464000Z), neither row hascreated_atorupdated_atAfter (6acf970)
Case 1, fresh deployment
Chat request: HTTP 200,
"id": "chatcmpl-EEgbdXuCeg0TNf27WWval2R22HSgH", 15 total tokenscurl -sS "http://localhost:47327/spend/logs?request_id=chatcmpl-EEgbdXuCeg0TNf27WWval2R22HSgH" -H "Authorization: Bearer sk-qa-1234"[{"request_id": "chatcmpl-EEgbdXuCeg0TNf27WWval2R22HSgH", "call_type": "acompletion", "api_key": "litellm_proxy_master_key", "spend": 7.200000000000001e-06, "total_tokens": 15, "prompt_tokens": 11, "completion_tokens": 4, "startTime": "2026-08-19T19:51:17.283000Z", "endTime": "2026-08-19T19:51:18.307000Z", "request_duration_ms": 1023, "completionStartTime": "2026-08-19T19:51:18.307000Z", "model": "openai/gpt-5.4-nano", "model_id": "df6a8a76a5a7f3b6eb5ac1d393b82a1266c52b78ee2da9692b8affe6d1f6bb9c", "model_group": "gpt-5.4-nano", "custom_llm_provider": "openai", "api_base": "https://api.openai.com/v1/", "user": "default_user_id", "cache_hit": "None", "cache_key": "Cache OFF", "request_tags": ["User-Agent: curl", "User-Agent: curl/8.7.1"], "team_id": "", "organization_id": "", "end_user": "", "requester_ip_address": "127.0.0.1", "session_id": "d3a12ffe-c816-4ccb-8538-e381a5820c3d", "status": "success", "mcp_namespaced_tool_name": null, "agent_id": null, "created_at": "2026-08-19T19:51:19.846000Z", "updated_at": "2026-08-19T19:51:19.846000Z"}]The row carries
created_atandupdated_at, set when the gateway wrote itcurl -sS "http://localhost:47327/spend/logs?start_date=2026-08-19&end_date=2026-08-20&summarize=false" -H "Authorization: Bearer sk-qa-1234": HTTP 200, 1 row,"created_at": "2026-08-19T19:51:19.846000Z", "updated_at": "2026-08-19T19:51:19.846000Z"Case 2, existing deployment upgraded
"id": "chatcmpl-EEgdJEFcCdiV7yHeOVYzUSKALKqmO";GET /spend/logs?request_id=chatcmpl-EEgdJEFcCdiV7yHeOVYzUSKALKqmOreturned the row with"startTime": "2026-08-19T19:53:00.521000Z"and nocreated_atorupdated_at; stopped that proxy20260818000000_add_spend_log_timestamps), sent the chat request: HTTP 200,"id": "chatcmpl-EEgeIqNjrWgOmX5CEaaWA3D9FXENH"curl -sS "http://localhost:46145/spend/logs?start_date=2026-08-19&end_date=2026-08-20&summarize=false" -H "Authorization: Bearer sk-qa-1234": HTTP 200, 2 rowschatcmpl-EEgeIqNjrWgOmX5CEaaWA3D9FXENH(new):"startTime": "2026-08-19T19:54:01.748000Z", "created_at": "2026-08-19T19:54:03.186000Z", "updated_at": "2026-08-19T19:54:03.186000Z"chatcmpl-EEgdJEFcCdiV7yHeOVYzUSKALKqmO(pre-existing):"startTime": "2026-08-19T19:53:00.521000Z", "created_at": "2026-08-19T12:53:46.024000Z", "updated_at": "2026-08-19T12:53:46.024000Z"(the migration time, stamped in the database server's local timezone, see Caveats)Observations from the run:
created_aton pre-existing rows is the migration time: caused by this PRcreated_attrailsendTimeby the batched flush (0.5-4s): caused by this PR/spend/logs/v2and/spend/logs/uirows lack the new fields: left alone by this PR/spend/logs/v2timestamps use+00:00,/spend/logsusesZ: left alone by this PR/spend/logs/v2?request_id=alone 400s without start and end dates: left alone by this PRType
🆕 New Feature
Caveats (if any)
/spend/logs/v2and/spend/logs/uido not return the new fields yetFinal Attestation
The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR
6acf970 passes /live-pr-risk
Note
Cursor Bugbot is generating a summary for commit 6acf970. Configure here.