Repository navigation
feat(proxy): add POST /v1/callbacks/logs to replay logging payloads through callbacks #31134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
40 commits merged into
litellm_internal_staging
from
litellm_proxy_callbacks_logs_ingest
Jun 24, 2026
Merged
Changes from 20 commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
37ac6ce
feat(proxy): add logging_endpoints package init
ishaan-jaff 8db98fe
feat(proxy): add POST /v1/callbacks/logs to replay logging payloads t…
ishaan-jaff ed3c448
feat(proxy): register callback_logs_router
ishaan-jaff 4c78a20
test(proxy): add logging_endpoints test package init
ishaan-jaff 63790ca
test(proxy): cover /v1/callbacks/logs replay, admin guard, and partia…
ishaan-jaff 64c0a12
refactor(proxy): move callback-logs request/response models to litell…
ishaan-jaff 86f85da
refactor(proxy): wrap callback-logs replay in CallbackLogsReplayer cl…
ishaan-jaff 0727631
test(proxy): update callback-logs tests for class-based replayer and …
ishaan-jaff 6bb369e
fix(proxy): cover /v1/callbacks/ in backend component allowlist
ishaan-jaff e791b2f
refactor(proxy): use builtin dict/list generics in callback-logs endp…
ishaan-jaff 834af9f
refactor(proxy): use builtin dict/list generics in callback-logs types
ishaan-jaff 123f138
chore(ui): regenerate schema.d.ts for /v1/callbacks/logs
ishaan-jaff e18342e
fix(proxy): force stream=False when replaying callback logs
ishaanj-berri 56ef43a
feat(litellm-rust): CustomLogger callback layer posting to /v1/callba…
ishaanj-berri 2af8b5e
feat(litellm-rust): RealTimeStreaming per-session log collector
ishaanj-berri f62b35e
feat(litellm-rust): wire realtime logging into the splice (lock-free …
ishaanj-berri 5130ad7
docs(litellm-rust): ai-gateway realtime logging architecture
ishaanj-berri a0fe2c5
docs(litellm-rust): document request-log egress to the LiteLLM contro…
ishaanj-berri acc8ffa
feat(litellm-rust): make log-egress tunables env-overridable
ishaanj-berri f79a5ed
docs(litellm-rust): document log-egress tuning env vars
ishaanj-berri b66dc84
docs(litellm-rust): require constants in a crate-level constants.rs
ishaanj-berri e58084c
refactor(litellm-rust): move ai-gateway constants into constants.rs
ishaanj-berri 0cc1896
ci: run logging_endpoints tests in the proxy-infra coverage shard
ishaanj-berri 5072e32
fix(litellm-rust): hash the master key before logging — never send th…
ishaanj-berri ba71c73
fix(litellm-rust): observe realtime logging on upstream events only
ishaanj-berri 17c5cb6
feat(proxy): bound callback-logs batch + return per-record failures
ishaanj-berri 4fcc554
chore(ui): regenerate schema.d.ts for CallbackLogFailure / failures f…
ishaanj-berri 7efe8c0
Merge remote-tracking branch 'upstream/litellm_internal_staging' into…
ishaanj-berri 13e12f1
fix(constants): make MAX_CALLBACK_LOG_RECORDS a plain constant
ishaanj-berri 2f122a9
docs(litellm-rust): trim ai-gateway ARCHITECTURE.md to one diagram + …
ishaanj-berri 10ce828
docs(litellm-rust): tighten the README request-logging section
ishaanj-berri 9dbc013
docs(litellm-rust): ARCHITECTURE.md is just the diagram (gateway = in…
ishaanj-berri d8733be
docs(litellm-rust): drop em-dashes from the request-logging section
ishaanj-berri e4f9066
Merge remote-tracking branch 'upstream/litellm_internal_staging' into…
ishaanj-berri 7f9f4fd
chore: stop tracking stray .coverage artifact and gitignore it
ishaanj-berri 616ca9f
feat(proxy): namespace rust-gateway routes under /v1/rust_control_plane
ishaanj-berri abd3db5
refactor(litellm-rust): POST logs to /v1/rust_control_plane/logs
ishaanj-berri 9cee1c1
docs(litellm-rust): update README to /v1/rust_control_plane/logs
ishaanj-berri 1e2a127
chore(ui): regenerate schema.d.ts for /v1/rust_control_plane/logs
ishaanj-berri a76a47d
refactor(litellm-rust): collapse env_positive_{usize,u64} into one ge…
ishaanj-berri File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # ai-gateway — architecture | ||
|
|
||
| The Rust gateway owns the **data plane** (the realtime WebSocket connection and the | ||
| LLM splice). It is **never** in the request path for logging: when a session ends it | ||
| hands a finished `StandardLoggingPayload` to a callback, and a background worker ships | ||
| that to the **LiteLLM Python service** over HTTP. Python does spend logs + the full | ||
| callback fan-out. This keeps the gateway non-blocking under high traffic — Python | ||
| slowness, batching, or downtime can never stall or crash a live session. | ||
|
|
||
| The realtime logging wrapper is a 1:1 port of Python's `RealTimeStreaming` | ||
| (`litellm/litellm_core_utils/realtime_streaming.py`): accumulate O(1) per-session | ||
| state during the call, emit **one** payload at close. | ||
|
|
||
| ## Data plane → logging plane → Python | ||
|
|
||
| ```mermaid | ||
| flowchart LR | ||
| subgraph DP["Data plane — Rust ai-gateway (hot path, never blocks)"] | ||
| direction TB | ||
| C["client WS<br/>(voice / text)"] | ||
| BR["routes/realtime — axum bridge<br/>socket ⇄ typed RealtimeEvent"] | ||
| SP["providers::realtime::realtime<br/>tokio::select! splice ⇄ OpenAI"] | ||
| RT["RealTimeStreaming.observe(event)<br/>O(1): model, usage, tool tallies<br/>drop the frame"] | ||
| UP["OpenAI realtime WS"] | ||
| C <--> BR <--> SP <--> UP | ||
| SP -. each event .-> RT | ||
| end | ||
|
|
||
| RT ==>|"on close: log_messages() builds ONE StandardLoggingPayload"| CB | ||
|
|
||
| subgraph LP["Logging plane — decoupled, async"] | ||
| direction TB | ||
| CB["CustomLogger fan-out<br/>(list of Arc dyn CustomLogger)"] | ||
| PX["LiteLLMPythonProxyAPILogger<br/>sink.try_send (non-blocking)"] | ||
| CH[["bounded byte-capped channel<br/>full → drop + counter"]] | ||
| WK["egress worker<br/>batch + gzip, pooled client"] | ||
| CB --> PX --> CH --> WK | ||
| end | ||
|
|
||
| WK ==>|"POST /v1/callbacks/logs (batched, keep-alive)"| PY | ||
|
|
||
| subgraph PY["LiteLLM Python service (scales horizontally)"] | ||
| direction TB | ||
| EP["/v1/callbacks/logs"] | ||
| RP["CallbackLogsReplayer →<br/>async_success_handler"] | ||
| OUT["spend logs + every callback<br/>(Langfuse, Datadog, …)"] | ||
| EP --> RP --> OUT | ||
| end | ||
| ``` | ||
|
|
||
| ## Why it holds up under high traffic | ||
|
|
||
| The request path does **zero** logging I/O. Each event costs one `observe()` call — | ||
| a `match` on the event type that drops the frame (audio deltas hit no branch). The | ||
| only network hop for logs is the worker's, off the hot path: | ||
|
|
||
| ```mermaid | ||
| flowchart LR | ||
| G1["gateway pod 1"] --> SVC | ||
| G2["gateway pod 2"] --> SVC | ||
| G3["gateway pod N"] --> SVC | ||
| SVC["LiteLLM Python service<br/>POST /v1/callbacks/logs<br/>(horizontally scaled)"] | ||
| ``` | ||
|
|
||
| - **Non-blocking emit** — handlers only `try_send`; if the channel is full they drop | ||
| the record and bump a metric, then keep serving. Python latency/downtime degrades | ||
| to dropped log lines + a rising counter, never a stalled or crashed gateway. | ||
| - **Bounded memory** — the channel is capped by **bytes** (not record count), and | ||
| `RealTimeStreaming` keeps **O(1)** per-session state (no buffered frames), so a | ||
| long voice call can't OOM the pod. | ||
| - **Batching + gzip** — the worker coalesces records and compresses, so many gateway | ||
| pods fan into a Python service that scales out independently. | ||
|
|
||
| ## Map to Python | ||
|
|
||
| | Python (`realtime_streaming.py` / `integrations/`) | Rust (this crate + `integrations/`) | | ||
| | --- | --- | | ||
| | `RealTimeStreaming` (wrapper, assemble-then-emit-once) | `RealTimeStreaming` (1:1) | | ||
| | `store_message` / `_collect_*` (accumulate) | `RealTimeStreaming.observe(&event)` (O(1)) | | ||
| | `log_messages()` at WS close (once) | `RealTimeStreaming::log_messages()` (once) | | ||
| | `CustomLogger` base class | `trait CustomLogger` (sync, typed args, `-> Result`) | | ||
| | `async_log_success_event` / `async_log_failure_event` | `log_success_event` / `log_failure_event` | | ||
| | a concrete integration (datadog, langfuse, …) | `LiteLLMPythonProxyAPILogger` (POSTs to the proxy) | | ||
| | in-process `async_success_handler` | `POST /v1/callbacks/logs` → same `async_success_handler` | | ||
|
|
||
| ## Where the code lives | ||
|
|
||
| ``` | ||
| crates/ai-gateway/ | ||
| routes/realtime/ # axum surface + splice service (data plane) | ||
| integrations/ # the callback layer (mirrors litellm/integrations/) | ||
| custom_logger.rs # trait CustomLogger — base contract | ||
| types.rs # typed StandardLoggingPayload, Usage, RequestMetadata, LoggingError | ||
| litellm_python_proxy_api.rs # LiteLLMPythonProxyAPILogger: bounded channel + egress worker | ||
| realtime/streaming.rs # RealTimeStreaming — per-session O(1) collector | ||
| ``` | ||
|
|
||
| The Python ingest endpoint (`POST /v1/callbacks/logs`) ships in | ||
| [BerriAI/litellm#31134](https://github.com/BerriAI/litellm/pull/31134); the realtime | ||
| splice in [#31135](https://github.com/BerriAI/litellm/pull/31135). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
litellm-rust/crates/ai-gateway/src/integrations/custom_logger.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| //! The `CustomLogger` trait — the Rust mirror of Python | ||
| //! `litellm/integrations/custom_logger.py::CustomLogger`. | ||
| //! | ||
| //! Synchronous (no `async_trait`): callbacks are O(1) enqueue-and-return so the | ||
| //! realtime splice never blocks on a logger. Default bodies are no-ops so a | ||
| //! logger can implement only the events it cares about. | ||
|
|
||
| use crate::integrations::types::{LogError, LoggingError, StandardLoggingPayload}; | ||
|
|
||
| pub trait CustomLogger: Send + Sync { | ||
| /// Record a successful call. Default: no-op. | ||
| fn log_success_event(&self, _payload: &StandardLoggingPayload) -> Result<(), LogError> { | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Record a failed call. Default: no-op. | ||
| fn log_failure_event( | ||
| &self, | ||
| _payload: &StandardLoggingPayload, | ||
| _error: &LoggingError, | ||
| ) -> Result<(), LogError> { | ||
| Ok(()) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.