Skip to content

feat(chat-ui): add personal Logs view scoped to the current user - #33829

Merged
krrish-berri-2 merged 3 commits into
litellm_internal_stagingfrom
litellm_chat_ui_my_logs
Jul 18, 2026
Merged

feat(chat-ui): add personal Logs view scoped to the current user#33829
krrish-berri-2 merged 3 commits into
litellm_internal_stagingfrom
litellm_chat_ui_my_logs

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

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)

Screenshots / Proof of Fix

UI-only change, verified end-to-end against a live proxy + dashboard hitting real Anthropic APIs (chat UI enabled with enable_chat_ui: true). Sent live chats, opened the Logs sidebar item, confirmed the table is scoped to only the signed-in user (other users' logs excluded), toggled 24h / 7d / 30d, paged through results, and opened the row details dialog

Logs demo

Steps to reproduce by hand:

  1. Start the proxy and the dashboard dev server, then open the chat UI at http://localhost:4000/ui/chat
  2. Click the new "Logs" item in the left sidebar (between "API Keys" and "Usage"), which routes to http://localhost:4000/ui/chat/logs
  3. Send a few chats first so there is data, then confirm the table lists your own requests with time, model, status, tokens, duration and cost, and that switching the 24h / 7d / 30d range and paging works
  4. Click a row and confirm the details dialog lazy-loads the request/response payload for that request_id (the request body shows when store_prompts_in_spend_logs is enabled, same as the main Spend Logs page)

Type

🆕 New Feature

Changes

Adds a "Logs" section to the pre-v0 chat UI that shows the signed-in user their own request logs, mirroring the spend-logs table but scoped to the caller so nobody else's logs are visible.

The panel reuses the existing /spend/logs/ui endpoint, always passing user_id equal to the current user. That endpoint already enforces non-admin scoping server-side (a non-admin caller is constrained to user = self, optionally OR'd with teams they administer), and for an admin the explicit user_id filter narrows the result to just their own rows, so the "only my logs" guarantee does not depend on the client. No backend changes were needed.

New LogsPanel fetches a page at a time with TanStack Query:

uiSpendLogsCall({
  start_date, end_date, page, page_size: 50,
  params: { user_id: currentUserId, sort_by: "startTime", sort_order: "desc" },
})

Wiring:

  • ChatShell.getChatRoutes() gains a logs route and a sidebar nav item (ScrollText icon) following the documented sidebar-nav pattern
  • src/app/chat/logs/page.tsx renders LogsPanel with accessToken / userId from ChatShellContext, matching the existing usage route
  • clicking a row opens a details dialog that lazily calls uiSpendLogDetailsCall(request_id) to show the request/response payload

Details dialog renders details.proxy_server_request ?? details.messages for the Request section, matching the main Spend Logs drawer (the backend stores the prompt in proxy_server_request for standard chat completions and leaves messages as {}). As with the main page, both payloads only populate when store_prompts_in_spend_logs is enabled on the proxy.

Review feedback addressed:

  • the detail query key now anchors on the selected log's own startTime instead of a moment()-derived range start, so an open dialog no longer refetches when the value drifts by a second on re-render
  • the panel now surfaces a distinct error state (with retry) when the logs query fails, instead of silently rendering the empty "No logs for this period" state

Tests: LogsPanel.test.tsx asserts the query is scoped to the current user_id, that rows and the empty state render, that the detail call fires only on row click, that the request payload falls back to proxy_server_request, and that a failed query renders the error state rather than the empty state; ChatShell tests cover the new route and active state.

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

Link to Devin session: https://app.devin.ai/sessions/d8f28b85a07e42fba149e7e51169bf0a
Requested by: @krrish-berri-2

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a "Logs" page to the pre-v0 chat UI that shows the signed-in user their own request logs, reusing the existing /spend/logs/ui endpoint and always passing user_id equal to the caller. No backend changes were needed, and the server already enforces per-user scoping for non-admin callers.

  • LogsPanel fetches paginated rows with TanStack Query, renders them in a table with time-range switching and paging, and lazily loads full request/response payload in a detail dialog on row click.
  • ChatShell gains a logs route and a sidebar nav item placed between "API Keys" and "Usage"; src/app/chat/logs/page.tsx wires the page to ChatShellContext.
  • Tests cover user-scoped query assertion, row/empty-state rendering, and lazy detail loading.

Confidence Score: 4/5

Safe to merge with minor follow-ups; the two issues in LogsPanel are polish-level and do not affect data correctness or security.

The detail-dialog query key uses a moment()-derived startDate that re-computes on every render, so any state update while the dialog is open can produce a new key and trigger an extra network round-trip. Separately, fetch failures in the main logs query silently fall through to the empty-state UI, giving users no signal that something went wrong. Both are visible UX issues but neither corrupts data or breaks routing.

ui/litellm-dashboard/src/components/chat/LogsPanel.tsx — detail query key and missing error branch

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/components/chat/LogsPanel.tsx New component for user-scoped logs. Has two issues: the detail-query key includes a volatile moment()-derived startDate that can trigger unnecessary refetches when the dialog is open, and there is no error-state branch in renderBody() — fetch failures silently render as the empty state.
ui/litellm-dashboard/src/app/chat/logs/page.tsx Thin page wrapper that reads accessToken/userId from ChatShellContext and renders LogsPanel — follows the same pattern as the existing usage page, no issues.
ui/litellm-dashboard/src/components/chat/ChatShell.tsx Adds logs route and NavItem for Logs between API Keys and Usage. Clean, follows existing sidebar-nav pattern.
ui/litellm-dashboard/src/components/chat/LogsPanel.test.tsx New test file covering scoping assertion, row rendering, empty state, and lazy detail-load on row click — good coverage of the key behaviors.
ui/litellm-dashboard/src/components/chat/ChatShell.test.tsx Adds tests for Logs nav-item click routing and active-state marking — appropriate additions that follow existing test patterns.
ui/litellm-dashboard/src/components/chat/ChatShell.serverRootPath.test.ts Single assertion added to verify server_root_path is prepended to the logs route — correct and consistent with the existing test structure.

Reviews (1): Last reviewed commit: "feat(chat-ui): add personal Logs view sc..." | Re-trigger Greptile

Comment on lines +261 to +265
const { data: detailData, isLoading: isDetailLoading } = useQuery({
queryKey: [LOGS_QUERY_KEY, "detail", accessToken, selectedLog?.request_id, startDate],
queryFn: () => uiSpendLogDetailsCall(accessToken, selectedLog!.request_id, startDate),
enabled: !!accessToken && !!selectedLog,
});

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.

P2 The detail query key includes startDate, which is re-derived from moment() on every render. When the dialog is open and any state update causes a re-render (e.g., the main logs query resolving), startDate can advance by a second, producing a new query key and triggering a redundant re-fetch of the detail payload. Using selectedLog?.startTime instead gives a stable, per-log anchor that doesn't drift — and is semantically more correct as a hint to the backend about where to find the record.

Suggested change
const { data: detailData, isLoading: isDetailLoading } = useQuery({
queryKey: [LOGS_QUERY_KEY, "detail", accessToken, selectedLog?.request_id, startDate],
queryFn: () => uiSpendLogDetailsCall(accessToken, selectedLog!.request_id, startDate),
enabled: !!accessToken && !!selectedLog,
});
const { data: detailData, isLoading: isDetailLoading } = useQuery({
queryKey: [LOGS_QUERY_KEY, "detail", accessToken, selectedLog?.request_id],
queryFn: () => uiSpendLogDetailsCall(accessToken, selectedLog!.request_id, selectedLog!.startTime),
enabled: !!accessToken && !!selectedLog,
});

enabled: !!accessToken && !!userId,
placeholderData: keepPreviousData,
};
const { data, isLoading } = useQuery(logsQueryOptions);

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.

P2 When the logs query fails, isLoading is false and rows is [] (because data is undefined), so renderBody falls through to LogsEmpty and shows "No logs for this period" to the user. A network error or a 4xx/5xx response is indistinguishable from a genuinely empty result set — add a branch for the isError case.

Suggested change
const { data, isLoading } = useQuery(logsQueryOptions);
const { data, isLoading, isError } = useQuery(logsQueryOptions);

Comment on lines +272 to +280
<>
<LogsTable rows={rows} onRowClick={setSelectedLog} />
<div className="mt-3 flex items-center justify-between">
<p className="m-0 text-xs text-muted-foreground">
{total.toLocaleString()} request{total === 1 ? "" : "s"}
{totalPages > 1 ? ` · Page ${page} of ${totalPages}` : ""}
</p>
{totalPages > 1 && (
<div className="flex gap-1">

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.

P2 renderBody has no error branch — pair the isError extraction (from the suggestion above) with a visible error state. Without it, any network error or API failure silently renders LogsEmpty ("No logs for this period"), giving users no indication that something went wrong and no prompt to retry.

@codspeed-hq

codspeed-hq Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_chat_ui_my_logs (9a20d4d) with litellm_internal_staging (e238e89)

Open in CodSpeed

…detail

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@codecov

codecov Bot commented Jul 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…error state)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@krrish-berri-2
krrish-berri-2 enabled auto-merge (squash) July 18, 2026 21:35
return moment().subtract(30, "days");
}

interface LogRow {

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.

how big of a lift would this be to have this use shared type from schema.d.ts so that we dont have to write the type by hand here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call. Small lift for the pragmatic version, done in follow-up #33858: it drops the hand-rolled LogRow and reuses the shared LogEntry type that the main Spend Logs UI already defines in view_logs/columns.tsx, so the row shape lives in one place.

On pulling straight from schema.d.ts: components["schemas"]["LiteLLM_SpendLogs"] exists, but it doesn't fully fit this table today. It omits status, request_duration_ms, and custom_llm_provider (which the endpoint returns and the table renders), and it types the /spend/logs/ui response as a bare LiteLLM_SpendLogs[] rather than the paginated { data, total, page, page_size, total_pages } wrapper the endpoint actually returns. That mismatch is why the main log UI keeps its own LogEntry. Making schema.d.ts the single source of truth would additionally need the backend Pydantic response model fixed (add the missing fields, model the pagination wrapper) and types regenerated, which is a larger, separate change I'm happy to pick up if you'd like it.

@krrish-berri-2
krrish-berri-2 merged commit d495da4 into litellm_internal_staging Jul 18, 2026
77 checks passed
@krrish-berri-2
krrish-berri-2 deleted the litellm_chat_ui_my_logs branch July 18, 2026 21:58
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