feat: trace infrastructure — spans, targets, request ID propagation - #7814
feat: trace infrastructure — spans, targets, request ID propagation#7814nnshah1 wants to merge 4 commits into
Conversation
Split HTTP router into system (debug-level) and inference (info-level) trace layers. Add make_inference_request_span with always-on request_span target, generate UUID when client omits x-dynamo-request-id, and rename span field to request_id. get_or_create_request_id now returns String (warns on invalid UUID instead of 400). Echo x-request-id header in responses. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WalkthroughRefactors request ID resolution and tracing: Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/runtime/src/pipeline/network/ingress/push_endpoint.rs (1)
96-106:⚠️ Potential issue | 🟡 MinorGenerate a fallback
request_idwhen headers are absent.This branch now creates an always-on
request_span, but it still records norequest_id. Any NATS payload without headers will therefore run with no distributed request ID, so the fallback ingress path still falls outside the new correlation scheme.Suggested fix
} else { - tracing::info_span!(target: "request_span", "handle_payload") + tracing::info_span!( + target: "request_span", + "handle_payload", + request_id = %uuid::Uuid::new_v4(), + ) };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/runtime/src/pipeline/network/ingress/push_endpoint.rs` around lines 96 - 106, When headers are missing, create and attach a generated request_id so the fallback tracing span participates in the correlation scheme: generate a unique ID (e.g. uuid::Uuid::new_v4().to_string()) and include it as the request_id field on the fallback span instead of the current plain tracing::info_span!(target: "request_span", "handle_payload"); mirror the same span field names used by make_handle_payload_span and keep component_name, endpoint_name, namespace, and instance_id in the span so the fallback path records the same metadata.
🧹 Nitpick comments (1)
lib/llm/src/http/service/service_v2.rs (1)
529-530: Consider logging client errors (4xx) at warn level for inference routes.Currently, both 4xx and 5xx responses are logged at
errorlevel. Client errors (e.g., 400 Bad Request, 401 Unauthorized, 422 Unprocessable Entity) typically indicate issues with the client's request rather than server failures. Logging these as errors could cause alert fatigue and obscure genuine server issues.♻️ Suggested change to differentiate client vs server errors
let on_response_inference = |response: &Response<Body>, latency: Duration, _span: &tracing::Span| { let status = response.status(); let latency_ms = latency.as_millis(); - if status.is_server_error() || status.is_client_error() { + if status.is_server_error() { tracing::error!(status = %status.as_u16(), latency_ms = %latency_ms, "http response sent"); + } else if status.is_client_error() { + tracing::warn!(status = %status.as_u16(), latency_ms = %latency_ms, "http response sent"); } else { tracing::info!(status = %status.as_u16(), latency_ms = %latency_ms, "http response sent"); } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/llm/src/http/service/service_v2.rs` around lines 529 - 530, The current tracing::error! call logs both client (4xx) and server (5xx) responses as errors; change the conditional to log client errors using tracing::warn! and server errors using tracing::error!—use the existing status and latency_ms variables (the status.is_client_error() and status.is_server_error() checks) to branch and emit tracing::warn!(status = %status.as_u16(), latency_ms = %latency_ms, "http response sent") for 4xx and tracing::error!(...) for 5xx so client-side request issues don't generate error-level alerts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/llm/src/http/service/openai.rs`:
- Around line 294-330: get_or_create_request_id currently relies on
get_distributed_tracing_context (the logging layer) so in READABLE setups it
will ignore a valid x-dynamo-request-id header and mint a new UUID; instead,
move the canonical request id into request extensions set by a
pre-handler/middleware (the same place make_inference_request_span should
read/write) and have get_or_create_request_id read from that extension first,
then fall back to validating DYNAMO_REQUEST_ID_HEADER from headers, and only
generate a new UUID if neither the extension nor a valid header exists;
update/introduce the middleware to populate the extension and remove dependence
on get_distributed_tracing_context in get_or_create_request_id.
In `@lib/runtime/src/logging.rs`:
- Around line 341-349: make_system_request_span currently creates a new Span
without preserving incoming trace context; update make_system_request_span(req:
&Request<B>) to extract the incoming "traceparent" (and related W3C headers)
from req.headers(), convert to a tracing::SpanContext or equivalent the project
uses (same approach as make_inference_request_span), and call
span.set_parent(...) (or use tracing::Span::current with the extracted context)
before returning so system routes join the caller's trace; reference the
existing make_inference_request_span implementation for the exact header parsing
and set_parent usage to replicate here.
---
Outside diff comments:
In `@lib/runtime/src/pipeline/network/ingress/push_endpoint.rs`:
- Around line 96-106: When headers are missing, create and attach a generated
request_id so the fallback tracing span participates in the correlation scheme:
generate a unique ID (e.g. uuid::Uuid::new_v4().to_string()) and include it as
the request_id field on the fallback span instead of the current plain
tracing::info_span!(target: "request_span", "handle_payload"); mirror the same
span field names used by make_handle_payload_span and keep component_name,
endpoint_name, namespace, and instance_id in the span so the fallback path
records the same metadata.
---
Nitpick comments:
In `@lib/llm/src/http/service/service_v2.rs`:
- Around line 529-530: The current tracing::error! call logs both client (4xx)
and server (5xx) responses as errors; change the conditional to log client
errors using tracing::warn! and server errors using tracing::error!—use the
existing status and latency_ms variables (the status.is_client_error() and
status.is_server_error() checks) to branch and emit tracing::warn!(status =
%status.as_u16(), latency_ms = %latency_ms, "http response sent") for 4xx and
tracing::error!(...) for 5xx so client-side request issues don't generate
error-level alerts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9995c547-125e-4341-8651-c6c7bd5c6c04
📒 Files selected for processing (6)
lib/llm/src/http/service/anthropic.rslib/llm/src/http/service/openai.rslib/llm/src/http/service/service_v2.rslib/runtime/src/logging.rslib/runtime/src/pipeline/network/ingress/push_endpoint.rslib/runtime/src/system_status_server.rs
…o-request-id In READABLE log mode (no DistributedTraceIdLayer), get_or_create_request_id now falls back to the validated header value for backwards compat. Also emits a deprecation warning (DEP #7812) when x-dynamo-request-id is sent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
make_system_request_span now extracts traceparent/tracestate headers and calls set_parent(), so management endpoints like /engine/* and /v1/loras can be correlated with the caller's trace. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
d645665 to
8f3cfd4
Compare
8f3cfd4 to
6a1e883
Compare
6a1e883 to
5307cb8
Compare
5307cb8 to
f871386
Compare
f871386 to
ad8c70c
Compare
|
@coderabbitai review |
|
@coderabbitai resolve |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/runtime/src/pipeline/network/ingress/http_endpoint.rs (1)
289-320:⚠️ Potential issue | 🟠 MajorReuse the shared
TraceParentparser here.This helper still copies the full
traceparentheader intotrace_idand never fillsparent_id, so the spawnedhandle_payloadspan records an invalid trace ID and drops the caller relationship. It also skips the UUID validation you just added forrequest-id. Sincehttp::HeaderMapalready implementsGenericHeaders, delegating toSelf::from_headers(headers)keeps the HTTP ingress path consistent.🔧 Proposed fix
impl TraceParent { pub fn from_axum_headers(headers: &HeaderMap) -> Self { - let mut traceparent = TraceParent::default(); - - if let Some(value) = headers.get("traceparent") - && let Ok(s) = value.to_str() - { - traceparent.trace_id = Some(s.to_string()); - } - - if let Some(value) = headers.get("tracestate") - && let Ok(s) = value.to_str() - { - traceparent.tracestate = Some(s.to_string()); - } - - if let Some(value) = headers.get("x-request-id") - && let Ok(s) = value.to_str() - { - traceparent.x_request_id = Some(s.to_string()); - } - - // Read request-id from internal headers, with fallback to deprecated x-dynamo-request-id - if let Some(value) = headers - .get("request-id") - .or_else(|| headers.get("x-dynamo-request-id")) - && let Ok(s) = value.to_str() - { - traceparent.request_id = Some(s.to_string()); - } - - traceparent + Self::from_headers(headers) } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/runtime/src/pipeline/network/ingress/http_endpoint.rs` around lines 289 - 320, The from_axum_headers implementation is duplicating parsing and incorrectly putting the full traceparent into trace_id while never setting parent_id and skipping the new request-id UUID validation; replace its body to delegate to the shared parser by calling Self::from_headers(headers) (http::HeaderMap already implements GenericHeaders) so trace_id/parent_id are populated correctly and request_id validation is reused instead of manual header copying in TraceParent::from_axum_headers.
🧹 Nitpick comments (1)
lib/llm/src/http/service/service_v2.rs (1)
509-517: Don’t log all 4xx responses aterror!.That will skew error-rate dashboards and page on normal control flow like 400/404/409/429. Reserve
error!for 5xx and downgrade 4xx towarn!orinfo!.🔧 Suggested change
- if status.is_server_error() || status.is_client_error() { + if status.is_server_error() { tracing::error!(status = %status.as_u16(), latency_ms = %latency_ms, "http response sent"); + } else if status.is_client_error() { + tracing::warn!(status = %status.as_u16(), latency_ms = %latency_ms, "http response sent"); } else { tracing::info!(status = %status.as_u16(), latency_ms = %latency_ms, "http response sent"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/llm/src/http/service/service_v2.rs` around lines 509 - 517, The shared on_response closure currently treats all 4xx as errors; change its logging level so only 5xx responses use tracing::error! and 4xx use tracing::warn! (or tracing::info! if you prefer less verbosity). Specifically, update the on_response closure (the lambda taking response: &Response<Body>, latency: Duration, _span: &tracing::Span) to check status.is_server_error() -> tracing::error!, else if status.is_client_error() -> tracing::warn!, else -> tracing::info!, keeping the same fields (status and latency_ms) and message text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/runtime/src/logging.rs`:
- Around line 474-477: The TCP/header path currently clones "request-id" /
"x-dynamo-request-id" verbatim into request_id; instead validate those values as
UUIDs (same validation used by TraceParent::from_headers) before recording or
propagating them. Update the logic that sets the request_id variable to attempt
parsing the header string with the project's UUID parser (e.g., Uuid::parse_str
or the same helper used by TraceParent::from_headers) and only assign/cloned
value when parsing succeeds; likewise apply the same validation for the
alternate "x-dynamo-request-id" header and for the similar logic around the
487-504 block so malformed/non-UUID IDs are rejected and not injected into spans
or outgoing headers.
- Around line 1116-1120: The code unconditionally forces request_span=trace via
filter_layer.add_directive("request_span=trace".parse().unwrap()), which causes
DistributedTraceIdLayer::on_enter() to run for all requests and triggers the
fail-fast OtelData panic; change this so the add_directive call is only executed
when the DYN_LOGGING_SPAN_EVENTS env flag is set (e.g., check
DYN_LOGGING_SPAN_EVENTS or similar config before calling
filter_layer.add_directive), leaving the default behavior unchanged when the
flag is absent; ensure references to OtelData/DistributedTraceIdLayer::on_enter
remain protected by that gate so the panic path remains opt-in.
---
Outside diff comments:
In `@lib/runtime/src/pipeline/network/ingress/http_endpoint.rs`:
- Around line 289-320: The from_axum_headers implementation is duplicating
parsing and incorrectly putting the full traceparent into trace_id while never
setting parent_id and skipping the new request-id UUID validation; replace its
body to delegate to the shared parser by calling Self::from_headers(headers)
(http::HeaderMap already implements GenericHeaders) so trace_id/parent_id are
populated correctly and request_id validation is reused instead of manual header
copying in TraceParent::from_axum_headers.
---
Nitpick comments:
In `@lib/llm/src/http/service/service_v2.rs`:
- Around line 509-517: The shared on_response closure currently treats all 4xx
as errors; change its logging level so only 5xx responses use tracing::error!
and 4xx use tracing::warn! (or tracing::info! if you prefer less verbosity).
Specifically, update the on_response closure (the lambda taking response:
&Response<Body>, latency: Duration, _span: &tracing::Span) to check
status.is_server_error() -> tracing::error!, else if status.is_client_error() ->
tracing::warn!, else -> tracing::info!, keeping the same fields (status and
latency_ms) and message text.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a611060a-dcf5-49d1-81c0-e320dd75631e
📒 Files selected for processing (4)
lib/llm/src/http/service/openai.rslib/llm/src/http/service/service_v2.rslib/runtime/src/logging.rslib/runtime/src/pipeline/network/ingress/http_endpoint.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/llm/src/http/service/openai.rs
✅ Actions performedComments resolved. Approval is disabled; enable |
…equest_id Use a single on_response callback for both system and inference routes (error for 4xx/5xx, info for success). Rename TraceParent and internal struct field x_dynamo_request_id → request_id for consistency (wire header x-dynamo-request-id unchanged). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@coderabbitai resolve Re: CodeRabbit comment on This is by design —
Re: TCP |
ad8c70c to
789d06b
Compare
|
Tip For best results, initiate chat on the files or code changes.
TCP [resolve] ✏️ Learnings added
🧠 Learnings used |
|
Superseded by follow-up PR on top of #7733 which was merged first. |
Summary
on_responsecallbacksmake_inference_request_spanwith always-onrequest_spantarget and empty span fields for downstream recordingget_or_create_request_idnow returnsString(warns on invalid UUID instead of 400)x_dynamo_request_id→request_idfor consistencyecho_request_id_headermiddleware to copyx-request-idfrom request to response headersrequest_span=tracefilter directive so request context is always visibleTest plan
cargo check --workspacepassescargo clippy -p dynamo-llm -p dynamo-runtime --no-depscleanPart 1 of 4 for DIS-1643: Consistent Error Tracing
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements