diff --git a/praxis/filters/src/filters/observe.rs b/praxis/filters/src/filters/observe.rs index aabfc6942..f6c7831bb 100644 --- a/praxis/filters/src/filters/observe.rs +++ b/praxis/filters/src/filters/observe.rs @@ -259,7 +259,7 @@ impl HttpFilter for CortexObserve { } o.done = true; // Before the response head, Praxis 0.8.0 gives a client that went - // away as status 499. After the head, it gives the written status and + // away as status 499, or as 0 when it wrote nothing. After the head, it gives the written status and // `upstream_reached` for both a client and an upstream that went away. // shortcut: a stream cut after the head is always `client_canceled` // (the frequent case: a user stop or a Cortex idle cut), so an diff --git a/praxis/filters/src/record/row.rs b/praxis/filters/src/record/row.rs index 56d9535bf..f20e80f26 100644 --- a/praxis/filters/src/record/row.rs +++ b/praxis/filters/src/record/row.rs @@ -544,7 +544,9 @@ impl Exchange { None => format!("request body over the {MAX_REQUEST_BODY}-byte buffer limit"), }; (413, ("proxy_error", String::new(), message)) - } else if downstream_gone || status == 499 { + // Status 0: Praxis wrote nothing to the client, so the client left + // before any answer (an upstream failure is written as 502). + } else if downstream_gone || status == 499 || status == 0 { (499, ("client_canceled", code, CLIENT_GONE.to_owned())) } else if upstream_gone { ( @@ -1138,6 +1140,21 @@ mod tests { } } + // Seen in the #1725 live check: the agent exited while the hop waited for + // the head, so Praxis wrote nothing and gave status 0. + #[test] + fn deferred_nothing_written_is_client_canceled() { + let f = fixture(); + let mut ex = exchange("GET", "api.example.com", &f.target, &f.req_headers); + let rows: Vec = ex.deferred(0, false, false).iter().map(json_of).collect(); + assert_eq!(rows.len(), 2); + assert_eq!(rows[1]["event"]["statusCode"], 499); + assert_eq!( + rows[1]["event"]["error"], + json!({"kind": "client_canceled", "message": CLIENT_GONE}) + ); + } + #[test] fn deferred_upstream_gone_is_upstream_error() { let rows = streaming_exchange().deferred(200, false, true);