fix: harden typed metrics review findings
This commit is contained in:
@@ -718,13 +718,20 @@ async fn mcp_post(
|
||||
let message = match payload {
|
||||
Ok(Json(message)) => message,
|
||||
Err(rejection) => {
|
||||
let mut request_metrics = McpRequestMetrics::invalid();
|
||||
let mut request_metrics = McpRequestMetrics::invalid(
|
||||
request_context.started_at(),
|
||||
request_context.exemplar(),
|
||||
);
|
||||
let response = rejection.into_response();
|
||||
request_metrics.complete(&response);
|
||||
return with_request_id_header(response, request_context.request_id());
|
||||
}
|
||||
};
|
||||
let mut request_metrics = McpRequestMetrics::new(&message);
|
||||
let mut request_metrics = McpRequestMetrics::new(
|
||||
&message,
|
||||
request_context.started_at(),
|
||||
request_context.exemplar(),
|
||||
);
|
||||
let transport_correlation = request_context.correlation;
|
||||
let transport_request_id = transport_correlation.request_id().to_string();
|
||||
info!(
|
||||
|
||||
@@ -19,32 +19,31 @@ pub(super) struct McpRequestMetrics {
|
||||
}
|
||||
|
||||
impl McpRequestMetrics {
|
||||
pub(super) fn invalid() -> Self {
|
||||
pub(super) fn invalid(started_at: Instant, exemplar: Option<ExemplarTraceId>) -> Self {
|
||||
Self {
|
||||
method: McpMethod::Invalid,
|
||||
response_mode: McpResponseMode::Unknown,
|
||||
outcome: McpOutcome::Aborted,
|
||||
started_at: Instant::now(),
|
||||
exemplar: None,
|
||||
started_at,
|
||||
exemplar,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn new(message: &Value) -> Self {
|
||||
pub(super) fn new(
|
||||
message: &Value,
|
||||
started_at: Instant,
|
||||
exemplar: Option<ExemplarTraceId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
method: normalized_mcp_method(message),
|
||||
response_mode: McpResponseMode::Unknown,
|
||||
outcome: McpOutcome::Aborted,
|
||||
started_at: Instant::now(),
|
||||
exemplar: None,
|
||||
started_at,
|
||||
exemplar,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn complete(&mut self, response: &Response) {
|
||||
self.exemplar = response
|
||||
.headers()
|
||||
.get("x-trace-id")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(ExemplarTraceId::parse);
|
||||
self.response_mode = response
|
||||
.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::transport::transport_response;
|
||||
#[tokio::test]
|
||||
async fn tool_error_response_includes_structured_context() {
|
||||
let message = json!({"jsonrpc": "2.0", "id": "req-1", "method": "tools/call"});
|
||||
let mut request_metrics = McpRequestMetrics::new(&message);
|
||||
let mut request_metrics = McpRequestMetrics::new(&message, std::time::Instant::now(), None);
|
||||
let response = tool_error_response(
|
||||
&message,
|
||||
ResponseMode::Json,
|
||||
@@ -69,7 +69,7 @@ async fn tool_error_response_includes_structured_context() {
|
||||
#[test]
|
||||
fn jsonrpc_error_over_http_200_is_not_counted_as_success() {
|
||||
let message = json!({"jsonrpc": "2.0", "id": 1, "method": "unsupported"});
|
||||
let mut request_metrics = McpRequestMetrics::new(&message);
|
||||
let mut request_metrics = McpRequestMetrics::new(&message, std::time::Instant::now(), None);
|
||||
let response = transport_response(
|
||||
axum::http::StatusCode::OK,
|
||||
jsonrpc_error(json!(1), -32601, "unsupported"),
|
||||
@@ -86,7 +86,7 @@ fn jsonrpc_error_over_http_200_is_not_counted_as_success() {
|
||||
#[test]
|
||||
fn metric_uses_actual_json_response_when_sse_request_falls_back() {
|
||||
let message = json!({"jsonrpc": "2.0", "id": 1, "method": "unsupported"});
|
||||
let mut request_metrics = McpRequestMetrics::new(&message);
|
||||
let mut request_metrics = McpRequestMetrics::new(&message, std::time::Instant::now(), None);
|
||||
let response = transport_response(
|
||||
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||
jsonrpc_error(json!(1), -32603, "internal error"),
|
||||
@@ -104,7 +104,7 @@ fn metric_uses_actual_json_response_when_sse_request_falls_back() {
|
||||
#[test]
|
||||
fn transport_failure_takes_priority_over_jsonrpc_payload() {
|
||||
let message = json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call"});
|
||||
let mut request_metrics = McpRequestMetrics::new(&message);
|
||||
let mut request_metrics = McpRequestMetrics::new(&message, std::time::Instant::now(), None);
|
||||
let response = transport_response(
|
||||
axum::http::StatusCode::TOO_MANY_REQUESTS,
|
||||
jsonrpc_error(json!(1), -32000, "rate limited"),
|
||||
@@ -121,7 +121,7 @@ fn transport_failure_takes_priority_over_jsonrpc_payload() {
|
||||
#[test]
|
||||
fn unfinished_request_is_classified_as_aborted() {
|
||||
let message = json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call"});
|
||||
let request_metrics = McpRequestMetrics::new(&message);
|
||||
let request_metrics = McpRequestMetrics::new(&message, std::time::Instant::now(), None);
|
||||
|
||||
assert_eq!(request_metrics.response_mode(), McpResponseMode::Unknown);
|
||||
assert_eq!(request_metrics.outcome(), McpOutcome::Aborted);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use axum::{extract::Request, http::HeaderValue, middleware::Next, response::Response};
|
||||
use crank_core::{CorrelationContext, RequestId, TraceContext};
|
||||
use crank_metrics::ExemplarTraceId;
|
||||
use crank_observability::{set_remote_trace_parent, with_request_correlation};
|
||||
use tracing::{Instrument, info_span};
|
||||
|
||||
@@ -10,12 +13,25 @@ const HEADER_X_TRACE_ID: axum::http::HeaderName = axum::http::HeaderName::from_s
|
||||
#[derive(Clone, Debug)]
|
||||
pub(super) struct RequestContext {
|
||||
pub(super) correlation: CorrelationContext,
|
||||
started_at: Instant,
|
||||
}
|
||||
|
||||
impl RequestContext {
|
||||
pub(super) fn request_id(&self) -> &str {
|
||||
self.correlation.request_id().as_str()
|
||||
}
|
||||
|
||||
pub(super) fn started_at(&self) -> Instant {
|
||||
self.started_at
|
||||
}
|
||||
|
||||
pub(super) fn exemplar(&self) -> Option<ExemplarTraceId> {
|
||||
self.correlation
|
||||
.trace_context()
|
||||
.is_sampled()
|
||||
.then(|| ExemplarTraceId::parse(self.correlation.trace_id().as_str()))
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn apply_request_context(mut request: Request, next: Next) -> Response {
|
||||
@@ -37,6 +53,7 @@ pub(super) async fn apply_request_context(mut request: Request, next: Next) -> R
|
||||
span.record("trace_id", trace_context.trace_id().as_str());
|
||||
let context = RequestContext {
|
||||
correlation: CorrelationContext::new(request_id, trace_context),
|
||||
started_at: Instant::now(),
|
||||
};
|
||||
request.extensions_mut().insert(context.clone());
|
||||
|
||||
@@ -51,6 +68,9 @@ pub(super) async fn apply_request_context(mut request: Request, next: Next) -> R
|
||||
if let Ok(value) = HeaderValue::from_str(context.correlation.trace_id().as_str()) {
|
||||
response.headers_mut().insert(HEADER_X_TRACE_ID, value);
|
||||
}
|
||||
if let Some(exemplar) = context.exemplar() {
|
||||
response.extensions_mut().insert(exemplar);
|
||||
}
|
||||
response
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user