feat: token counts on span + cancellation WARN logging [DIS-1643] - #7735
Conversation
d65fbf5 to
8c6da06
Compare
08a87a2 to
9f30466
Compare
8c6da06 to
3f1ba55
Compare
9f30466 to
5d9b2fb
Compare
5d9b2fb to
28e29c4
Compare
3f1ba55 to
1e563d5
Compare
28e29c4 to
cce6aa3
Compare
1e563d5 to
f4e66f2
Compare
cce6aa3 to
d248a4e
Compare
f4e66f2 to
fdce96b
Compare
d248a4e to
9780c18
Compare
fdce96b to
72021e2
Compare
9780c18 to
ac29172
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
WalkthroughThe changes enhance observability by upgrading disconnect logging from trace to warn level with structured context, adding time-to-first-token (TTFT) and inter-token latency (ITL) tracking to metrics collection, and recording request metrics to tracing spans for log correlation across inflight and response handling phases. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/llm/src/http/service/metrics.rs (1)
917-922:⚠️ Potential issue | 🔴 CriticalFix missing
request_idargument inaudio_speechhandler.The
create_inflight_guardcall atopenai.rs:2306in theaudio_speechhandler is missing the requiredrequest_id: Stringargument. The variable is available at that point (extracted and reassigned around lines 2278–2282), so update the call to:.create_inflight_guard(&model, Endpoint::Audios, streaming, request_id.clone())🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/llm/src/http/service/metrics.rs` around lines 917 - 922, The call to create_inflight_guard in the audio_speech handler is missing the required request_id argument; update the invocation in the audio_speech handler (openai.rs) to pass the model by reference and the request id (use request_id.clone()) and the Audios endpoint, e.g. call create_inflight_guard(&model, Endpoint::Audios, streaming, request_id.clone()) so the signature pub fn create_inflight_guard(self: Arc<Self>, model: &str, endpoint: Endpoint, streaming: bool, request_id: String) is satisfied.
🧹 Nitpick comments (1)
lib/llm/src/http/service/metrics.rs (1)
1382-1404: Span recording relies on collector being dropped while span is still active.This implementation calls
tracing::Span::current()in Drop, assuming the request span is still the current span. Based on the codebase architecture (TraceLayer applies the span at middleware layer, collector is created inside handlers and dropped when handler returns/stream ends), this assumption holds for normal request flows.However, if the collector is somehow moved to a different async task or outlives the span scope, these recordings would go to the wrong span or be lost. Consider adding a brief code comment documenting this contract for future maintainers.
📝 Suggested documentation comment
// Publish final OSL when the collector is dropped self.metrics .output_sequence_length .with_label_values(&[&self.model]) .observe(self.osl as f64); - // Record request summary on the enclosing span. - // InflightGuard::Drop and on_response logs will inherit these. + // Record request summary on the enclosing span. + // IMPORTANT: This assumes the collector is dropped while the request span + // (created by TraceLayer middleware) is still active. The collector must not + // be moved to a separate task or outlive the handler's span scope. + // InflightGuard::Drop and on_response logs will inherit these fields. let span = tracing::Span::current();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/llm/src/http/service/metrics.rs` around lines 1382 - 1404, The Drop implementation in InflightGuard (where tracing::Span::current() is used to record fields like input_tokens/output_tokens/ttft_ms/avg_itl_ms/prefill_worker_id/decode_worker_id) assumes the request span is still the current span; add a clear comment above this block (or above InflightGuard::drop) documenting the lifetime contract: that the collector must be dropped while the request span remains active and must not be moved to another async task or outlive the handler scope, and note potential misrecording if that contract is violated and how callers should ensure correct ownership/awaiting to preserve span activity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@lib/llm/src/http/service/metrics.rs`:
- Around line 917-922: The call to create_inflight_guard in the audio_speech
handler is missing the required request_id argument; update the invocation in
the audio_speech handler (openai.rs) to pass the model by reference and the
request id (use request_id.clone()) and the Audios endpoint, e.g. call
create_inflight_guard(&model, Endpoint::Audios, streaming, request_id.clone())
so the signature pub fn create_inflight_guard(self: Arc<Self>, model: &str,
endpoint: Endpoint, streaming: bool, request_id: String) is satisfied.
---
Nitpick comments:
In `@lib/llm/src/http/service/metrics.rs`:
- Around line 1382-1404: The Drop implementation in InflightGuard (where
tracing::Span::current() is used to record fields like
input_tokens/output_tokens/ttft_ms/avg_itl_ms/prefill_worker_id/decode_worker_id)
assumes the request span is still the current span; add a clear comment above
this block (or above InflightGuard::drop) documenting the lifetime contract:
that the collector must be dropped while the request span remains active and
must not be moved to another async task or outlive the handler scope, and note
potential misrecording if that contract is violated and how callers should
ensure correct ownership/awaiting to preserve span activity.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 536cf9a9-f270-4ebd-8924-47f4b13363e5
📒 Files selected for processing (3)
lib/llm/src/http/service/disconnect.rslib/llm/src/http/service/metrics.rslib/runtime/src/logging.rs
72021e2 to
6cfa7ab
Compare
ac29172 to
3ca1685
Compare
6cfa7ab to
faee421
Compare
3ca1685 to
42c8201
Compare
- Rename make_request_span → make_inference_request_span with target: "request_span" (always on via filter directive) - Add make_system_request_span with target: "system_span" (debug level) - Add "request_span=trace" directive in filters() - Simplify get_or_create_request_id() — validates UUID, returns Result<String, String> - Update worker spans to target: "request_span" - Worker system_status_server uses make_system_request_span Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename make_request_span → make_inference_request_span with target: "request_span" (always on via filter directive) - Add make_system_request_span with target: "system_span" (debug level) - Add "request_span=trace" directive in filters() - Simplify get_or_create_request_id() — validates UUID, returns Result<String, String> - Update worker spans to target: "request_span" - Worker system_status_server uses make_system_request_span Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- InflightGuard logs "request received" (INFO) and "request completed" (INFO success, ERROR failure) with structured fields - Split service_v2 router into system/inference with separate TraceLayer - System endpoints: debug spans, inference: info spans with "http response sent" - Worker logs "request received"/"request completed" at INFO - All inference errors log at ERROR level Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ResponseMetricCollector records on span in Drop: input_tokens, output_tokens, ttft_ms, avg_itl_ms, prefill_worker_id, decode_worker_id - Stores already-computed TTFT and accumulates ITL for average - WARN log at cancellation point with request context - Connection monitor disconnects upgraded from TRACE to WARN Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
42c8201 to
0943ded
Compare
Summary
ResponseMetricCollectorrecords request summary on the enclosing span in Drop:input_tokens,output_tokens,ttft_ms,avg_itl_ms,prefill_worker_id,decode_worker_iddisconnect.rswith request contextDepends on: #7734
Fields on "request completed" Log
input_tokensoutput_tokensttft_msavg_itl_msprefill_worker_iddecode_worker_idExample: Streaming Success (with all fields)
{ "level": "INFO", "message": "request completed", "status": "success", "request_id": "32691d61-...", "model": "qwen/qwen3-0.6b", "endpoint": "chat_completions", "request_type": "stream", "elapsed_ms": "20", "input_tokens": "9", "output_tokens": "50", "ttft_ms": "5.85", "avg_itl_ms": "0.29", "trace_id": "bca97f5e..." }Example: Cancellation
{"level":"ERROR","message":"request completed","status":"error","error_type":"cancelled","error_detail":"client disconnected before completion","elapsed_ms":"230"} {"level":"WARN","message":"Stream closed unexpectedly; issuing cancellation"}Example: Worker Crash (partial tokens before crash)
{"level":"ERROR","message":"request completed","status":"error","error_type":"internal","error_detail":"internal server error during processing","elapsed_ms":"556","input_tokens":"9","output_tokens":"4"}Performance
ttft_ms: oneOption<f64>assignment when TTFT is already computed (same code path)itl_sum_secs/itl_count: one f64 add + one u64 increment per chunk (negligible, same path as histogram publish)span.record()calls in Drop, once per request at cleanupFiles Changed
lib/llm/src/http/service/metrics.rslib/llm/src/http/service/disconnect.rslib/runtime/src/logging.rsFollow-up
🤖 Generated with Claude Code
Linear: DIS-1643
Summary by CodeRabbit