Skip to content

feat(spend-logs): add lifecycle timestamps - #37361

Merged
mateo-berri merged 3 commits into
BerriAI:litellm_internal_stagingfrom
sytianhe:litellm_spend_log_timestamps
Aug 19, 2026
Merged

feat(spend-logs): add lifecycle timestamps#37361
mateo-berri merged 3 commits into
BerriAI:litellm_internal_stagingfrom
sytianhe:litellm_spend_log_timestamps

Conversation

@sytianhe

@sytianhe sytianhe commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Spend-log exports lack database ingestion timestamps
  • Incremental pipelines cannot identify newly persisted rows

How it solves it:

  • Adds database-managed created_at and updated_at columns
  • Exposes both timestamps in spend-log API models

User 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

  1. They send POST https://litellm-domain/v1/chat/completions with a model and one message and get a 200 with an id
  2. They send GET https://litellm-domain/spend/logs?request_id= with the master key
  3. The row comes back with startTime and endTime only, 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 it

After: the same export carries created_at and updated_at, so the incremental job checkpoints on when rows were written

  1. They send POST https://litellm-domain/v1/chat/completions with a model and one message and get a 200 with an id
  2. They send GET https://litellm-domain/spend/logs?request_id= with the master key
  3. The row now also carries created_at and updated_at ISO timestamps set when the gateway wrote it, so the job selects rows with created_at after its last checkpoint and stops re-exporting overlapping windows

Relevant issues

Linear ticket

Resolves LIT-5819

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to 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:

model_list:
  - model_name: gpt-5.4-nano
    litellm_params:
      model: openai/gpt-5.4-nano
      api_key: os.environ/OPENAI_API_KEY
general_settings:
  master_key: sk-qa-1234

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 with messages, response, metadata, and proxy_server_request trimmed

Before (d03ef8b)

Case 1, fresh deployment

  1. Chat request: HTTP 200, "id": "chatcmpl-EEgahJKQoiuQdhvuDgoxxCh9EKDX4", 15 total tokens

  2. curl -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_at or updated_at key on the row

  3. 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, 1 row, same shape as step 2, no created_at or updated_at

Case 2, existing deployment upgraded

  1. Restarted the proxy against the same database ("No pending migrations to apply"), sent the chat request again: HTTP 200, "id": "chatcmpl-EEgbvxmg1r3xleeBksJxIxJIhRiyP"
  2. 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-EEgbvxmg1r3xleeBksJxIxJIhRiyP with startTime 2026-08-19T19:51:34.840000Z, chatcmpl-EEgahJKQoiuQdhvuDgoxxCh9EKDX4 with startTime 2026-08-19T19:50:18.464000Z), neither row has created_at or updated_at

After (6acf970)

Case 1, fresh deployment

  1. Chat request: HTTP 200, "id": "chatcmpl-EEgbdXuCeg0TNf27WWval2R22HSgH", 15 total tokens

  2. curl -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_at and updated_at, set when the gateway wrote it

  3. curl -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

  1. Seeded a database with a proxy at the merge base: chat request HTTP 200, "id": "chatcmpl-EEgdJEFcCdiV7yHeOVYzUSKALKqmO"; GET /spend/logs?request_id=chatcmpl-EEgdJEFcCdiV7yHeOVYzUSKALKqmO returned the row with "startTime": "2026-08-19T19:53:00.521000Z" and no created_at or updated_at; stopped that proxy
  2. Booted the proxy at this tip against that database (the boot applied 20260818000000_add_spend_log_timestamps), sent the chat request: HTTP 200, "id": "chatcmpl-EEgeIqNjrWgOmX5CEaaWA3D9FXENH"
  3. 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 rows
    • chatcmpl-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:

  • Backfilled created_at on pre-existing rows is the migration time: caused by this PR
  • Backfill follows the DB server timezone, read back as UTC: caused by this PR
  • created_at trails endTime by the batched flush (0.5-4s): caused by this PR
  • /spend/logs/v2 and /spend/logs/ui rows lack the new fields: left alone by this PR
  • /spend/logs/v2 timestamps use +00:00, /spend/logs uses Z: left alone by this PR
  • /spend/logs/v2?request_id= alone 400s without start and end dates: left alone by this PR

Type

🆕 New Feature

Caveats (if any)

  • Existing rows receive the migration deployment timestamp
  • That backfill is stamped in the DB server's timezone, read as UTC
  • Spend logs are normally immutable, so timestamps initially match
  • /spend/logs/v2 and /spend/logs/ui do not return the new fields yet

Final 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.

@sytianhe
sytianhe requested a review from a team August 18, 2026 21:17
@CLAassistant

CLAassistant commented Aug 18, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds database-managed lifecycle timestamps to spend logs and exposes them through the API models.

  • Adds an additive migration for non-null created_at and updated_at columns.
  • Synchronizes the lifecycle fields across all three Prisma schema copies.
  • Updates Python and generated TypeScript response types and adds model parsing coverage.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported schema synchronization issue is fixed at current HEAD.

Important Files Changed

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

Comment thread litellm/proxy/schema.prisma
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@sytianhe

Copy link
Copy Markdown
Contributor Author

@greptileai Please re-review the latest commit; the root schema now matches both runtime copies and schema-sync CI passes

@codspeed-hq

codspeed-hq Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing sytianhe:litellm_spend_log_timestamps (6acf970) with litellm_internal_staging (4d100bd)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (4bb3152) during the generation of this report, so 4d100bd was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@mateo-berri

Copy link
Copy Markdown
Contributor

@greptileai

@mateo-berri

Copy link
Copy Markdown
Contributor

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 mateo-berri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks for the contribution!

@mateo-berri
mateo-berri enabled auto-merge August 19, 2026 20:03
@mateo-berri
mateo-berri merged commit 0f19b5b into BerriAI:litellm_internal_staging Aug 19, 2026
80 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants