From 1eb9af14a34baeb814dc53ca3b9ca38a0594231c Mon Sep 17 00:00:00 2001 From: Tomer Moran Date: Thu, 9 Jul 2026 14:15:27 -0400 Subject: [PATCH 1/2] Fix bounded HTTP log cursor timestamps HTTP log history queries use cursor-style beforeDate and anchorDate fields. The CLI parsed --until into UTC and serialized it with to_rfc3339(), producing +00:00, while the HTTP logs cursor API expects the same Z-suffixed timestamp format already used by network-flow logs. As a result, railway logs --http --until returned the backend's generic Problem processing request error. This change shares the cursor timestamp formatter between HTTP and network-flow log queries and adds a regression test for bounded HTTP log windows. The original ask was to prepare a Railway CLI fix branch without opening a PR. --- src/controllers/deployment.rs | 59 +++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/controllers/deployment.rs b/src/controllers/deployment.rs index c131c2e15..bbdf4a800 100644 --- a/src/controllers/deployment.rs +++ b/src/controllers/deployment.rs @@ -74,10 +74,26 @@ struct NetworkFlowLogWindow { after_limit: Option, } -fn format_network_flow_log_timestamp(date: DateTime) -> String { +#[derive(Debug, PartialEq, Eq)] +struct HttpLogWindow { + before_date: Option, + anchor_date: Option, +} + +fn format_log_cursor_timestamp(date: DateTime) -> String { date.to_rfc3339_opts(SecondsFormat::Nanos, true) } +fn http_log_window( + start_date: Option>, + end_date: Option>, +) -> HttpLogWindow { + HttpLogWindow { + before_date: start_date.map(format_log_cursor_timestamp), + anchor_date: end_date.map(format_log_cursor_timestamp), + } +} + fn network_flow_log_window( limit: Option, start_date: Option>, @@ -89,26 +105,26 @@ fn network_flow_log_window( match (start_date, end_date) { (Some(start), Some(end)) => NetworkFlowLogWindow { before_limit, - before_date: Some(format_network_flow_log_timestamp(start)), - anchor_date: Some(format_network_flow_log_timestamp(end)), - after_date: Some(format_network_flow_log_timestamp(end)), + before_date: Some(format_log_cursor_timestamp(start)), + anchor_date: Some(format_log_cursor_timestamp(end)), + after_date: Some(format_log_cursor_timestamp(end)), after_limit: Some(0), }, (Some(start), None) => NetworkFlowLogWindow { before_limit, - before_date: Some(format_network_flow_log_timestamp(start)), - anchor_date: Some(format_network_flow_log_timestamp(now)), - after_date: Some(format_network_flow_log_timestamp(now)), + before_date: Some(format_log_cursor_timestamp(start)), + anchor_date: Some(format_log_cursor_timestamp(now)), + after_date: Some(format_log_cursor_timestamp(now)), after_limit: Some(0), }, (None, Some(end)) => NetworkFlowLogWindow { before_limit, - before_date: Some(format_network_flow_log_timestamp( + before_date: Some(format_log_cursor_timestamp( DateTime::::from_timestamp(0, 0) .expect("Unix epoch should be a valid timestamp"), )), - anchor_date: Some(format_network_flow_log_timestamp(end)), - after_date: Some(format_network_flow_log_timestamp(end)), + anchor_date: Some(format_log_cursor_timestamp(end)), + after_date: Some(format_log_cursor_timestamp(end)), after_limit: Some(0), }, (None, None) => NetworkFlowLogWindow { @@ -178,12 +194,13 @@ pub async fn fetch_http_logs( mut on_log: impl FnMut(queries::http_logs::HttpLogFields), ) -> Result<()> { let before_limit = params.limit.unwrap_or(500); + let window = http_log_window(params.start_date, params.end_date); let vars = queries::http_logs::Variables { deployment_id: params.deployment_id, filter: params.filter, before_limit, - before_date: params.start_date.map(|date| date.to_rfc3339()), - anchor_date: params.end_date.map(|date| date.to_rfc3339()), + before_date: window.before_date, + anchor_date: window.anchor_date, after_date: None, after_limit: None, }; @@ -408,7 +425,7 @@ impl NetworkFlowLogDedupe { fn network_flow_stream_before_date(max_capture_end: Option>) -> String { let anchor = max_capture_end.unwrap_or_else(Utc::now); - format_network_flow_log_timestamp( + format_log_cursor_timestamp( anchor - ChronoDuration::seconds(NETWORK_FLOW_STREAM_LOOKBACK_SECONDS), ) } @@ -812,6 +829,22 @@ mod tests { assert_eq!(before_date, "2026-06-18T04:42:30.000000000Z"); } + #[test] + fn test_http_log_window_uses_cursor_timestamp_format() { + let start = dt("2026-06-18T04:41:00Z"); + let end = dt("2026-06-18T04:42:00Z"); + + let window = http_log_window(Some(start), Some(end)); + + assert_eq!( + window, + HttpLogWindow { + before_date: Some("2026-06-18T04:41:00.000000000Z".to_string()), + anchor_date: Some("2026-06-18T04:42:00.000000000Z".to_string()), + } + ); + } + #[test] fn test_update_max_network_flow_capture_end_ignores_older_rows() { let mut max_capture_end = Some(dt("2026-06-18T04:43:00Z")); From 8465722c26f530b230cffde7adfa3249f82a12e3 Mon Sep 17 00:00:00 2001 From: Tomer Moran Date: Thu, 9 Jul 2026 14:21:29 -0400 Subject: [PATCH 2/2] Reduce HTTP log timestamp fix scope Keep the bounded HTTP log fix as small as possible by preserving the existing network-flow log helper and window structure. The only runtime change now maps HTTP log beforeDate and anchorDate through a tiny HTTP timestamp formatter that emits the Z-suffixed cursor timestamp format accepted by the API. This responds to the request to minimize the patch while retaining the existing function structure. --- src/controllers/deployment.rs | 57 +++++++++++------------------------ 1 file changed, 18 insertions(+), 39 deletions(-) diff --git a/src/controllers/deployment.rs b/src/controllers/deployment.rs index bbdf4a800..4449ce1e1 100644 --- a/src/controllers/deployment.rs +++ b/src/controllers/deployment.rs @@ -74,24 +74,12 @@ struct NetworkFlowLogWindow { after_limit: Option, } -#[derive(Debug, PartialEq, Eq)] -struct HttpLogWindow { - before_date: Option, - anchor_date: Option, -} - -fn format_log_cursor_timestamp(date: DateTime) -> String { +fn format_network_flow_log_timestamp(date: DateTime) -> String { date.to_rfc3339_opts(SecondsFormat::Nanos, true) } -fn http_log_window( - start_date: Option>, - end_date: Option>, -) -> HttpLogWindow { - HttpLogWindow { - before_date: start_date.map(format_log_cursor_timestamp), - anchor_date: end_date.map(format_log_cursor_timestamp), - } +fn format_http_log_timestamp(date: DateTime) -> String { + format_network_flow_log_timestamp(date) } fn network_flow_log_window( @@ -105,26 +93,26 @@ fn network_flow_log_window( match (start_date, end_date) { (Some(start), Some(end)) => NetworkFlowLogWindow { before_limit, - before_date: Some(format_log_cursor_timestamp(start)), - anchor_date: Some(format_log_cursor_timestamp(end)), - after_date: Some(format_log_cursor_timestamp(end)), + before_date: Some(format_network_flow_log_timestamp(start)), + anchor_date: Some(format_network_flow_log_timestamp(end)), + after_date: Some(format_network_flow_log_timestamp(end)), after_limit: Some(0), }, (Some(start), None) => NetworkFlowLogWindow { before_limit, - before_date: Some(format_log_cursor_timestamp(start)), - anchor_date: Some(format_log_cursor_timestamp(now)), - after_date: Some(format_log_cursor_timestamp(now)), + before_date: Some(format_network_flow_log_timestamp(start)), + anchor_date: Some(format_network_flow_log_timestamp(now)), + after_date: Some(format_network_flow_log_timestamp(now)), after_limit: Some(0), }, (None, Some(end)) => NetworkFlowLogWindow { before_limit, - before_date: Some(format_log_cursor_timestamp( + before_date: Some(format_network_flow_log_timestamp( DateTime::::from_timestamp(0, 0) .expect("Unix epoch should be a valid timestamp"), )), - anchor_date: Some(format_log_cursor_timestamp(end)), - after_date: Some(format_log_cursor_timestamp(end)), + anchor_date: Some(format_network_flow_log_timestamp(end)), + after_date: Some(format_network_flow_log_timestamp(end)), after_limit: Some(0), }, (None, None) => NetworkFlowLogWindow { @@ -194,13 +182,12 @@ pub async fn fetch_http_logs( mut on_log: impl FnMut(queries::http_logs::HttpLogFields), ) -> Result<()> { let before_limit = params.limit.unwrap_or(500); - let window = http_log_window(params.start_date, params.end_date); let vars = queries::http_logs::Variables { deployment_id: params.deployment_id, filter: params.filter, before_limit, - before_date: window.before_date, - anchor_date: window.anchor_date, + before_date: params.start_date.map(format_http_log_timestamp), + anchor_date: params.end_date.map(format_http_log_timestamp), after_date: None, after_limit: None, }; @@ -425,7 +412,7 @@ impl NetworkFlowLogDedupe { fn network_flow_stream_before_date(max_capture_end: Option>) -> String { let anchor = max_capture_end.unwrap_or_else(Utc::now); - format_log_cursor_timestamp( + format_network_flow_log_timestamp( anchor - ChronoDuration::seconds(NETWORK_FLOW_STREAM_LOOKBACK_SECONDS), ) } @@ -830,18 +817,10 @@ mod tests { } #[test] - fn test_http_log_window_uses_cursor_timestamp_format() { - let start = dt("2026-06-18T04:41:00Z"); - let end = dt("2026-06-18T04:42:00Z"); - - let window = http_log_window(Some(start), Some(end)); - + fn test_http_log_timestamp_uses_cursor_timestamp_format() { assert_eq!( - window, - HttpLogWindow { - before_date: Some("2026-06-18T04:41:00.000000000Z".to_string()), - anchor_date: Some("2026-06-18T04:42:00.000000000Z".to_string()), - } + format_http_log_timestamp(dt("2026-06-18T04:41:00Z")), + "2026-06-18T04:41:00.000000000Z" ); }