feat(gateway): harden failover and payload handling

Retry pre-response transport failures across candidates with an explicit stop policy, and propagate end-to-end timing into usage records and UI diagnostics.

Remove legacy body, import, cookie, PII, and tunnel replay caps while preserving optional operator-configured gateway limits.
This commit is contained in:
elky
2026-07-30 01:03:27 +08:00
parent a97acc07fc
commit a04673a90d
80 changed files with 3640 additions and 1236 deletions
@@ -165,7 +165,7 @@ impl UsageBodyCaptureEngine {
apply_usage_body_capture_limit(
UsageBodyField::RequestBody,
"request",
self.policy.max_request_body_bytes,
None,
payload.request_body,
payload.request_body_ref,
payload.request_body_state,
@@ -174,7 +174,7 @@ impl UsageBodyCaptureEngine {
apply_usage_body_capture_limit(
UsageBodyField::ProviderRequestBody,
"provider_request",
self.policy.max_request_body_bytes,
None,
payload.provider_request_body,
payload.provider_request_body_ref,
payload.provider_request_body_state,
@@ -183,7 +183,7 @@ impl UsageBodyCaptureEngine {
apply_usage_body_capture_limit(
UsageBodyField::ResponseBody,
"response",
self.policy.max_response_body_bytes,
None,
payload.response_body,
payload.response_body_ref,
payload.response_body_state,
@@ -192,7 +192,7 @@ impl UsageBodyCaptureEngine {
apply_usage_body_capture_limit(
UsageBodyField::ClientResponseBody,
"client_response",
self.policy.max_response_body_bytes,
None,
payload.client_response_body,
payload.client_response_body_ref,
payload.client_response_body_state,
@@ -360,6 +360,10 @@ fn copy_allowed_metadata_fields(source: &Map<String, Value>, target: &mut Map<St
copy_number(source, target, "client_response_body_base64_bytes");
copy_non_null_value(source, target, "body_size");
copy_number(source, target, "client_response_status_code");
copy_number(source, target, "end_to_end_time_ms");
copy_number(source, target, "end_to_end_first_byte_time_ms");
copy_bool(source, target, "transport_error");
copy_non_empty_string(source, target, "transport_error_type");
copy_non_null_value(source, target, "billing_snapshot");
copy_non_empty_string(source, target, "billing_snapshot_schema_version");
copy_non_empty_string(source, target, "billing_snapshot_status");
@@ -412,6 +416,10 @@ fn move_allowed_metadata_fields(mut source: Map<String, Value>, target: &mut Map
remove_number(&mut source, target, "client_response_body_base64_bytes");
remove_non_null_value(&mut source, target, "body_size");
remove_number(&mut source, target, "client_response_status_code");
remove_number(&mut source, target, "end_to_end_time_ms");
remove_number(&mut source, target, "end_to_end_first_byte_time_ms");
remove_bool(&mut source, target, "transport_error");
remove_non_empty_string(&mut source, target, "transport_error_type");
remove_non_null_value(&mut source, target, "billing_snapshot");
remove_non_empty_string(&mut source, target, "billing_snapshot_schema_version");
remove_non_empty_string(&mut source, target, "billing_snapshot_status");
@@ -776,6 +784,10 @@ mod tests {
"provider_request_body_base64_bytes": 512,
"provider_response_body_base64_bytes": 1024,
"client_response_body_base64_bytes": 2048,
"end_to_end_time_ms": 10626,
"end_to_end_first_byte_time_ms": 10120,
"transport_error": true,
"transport_error_type": "connect_timeout",
"body_size": {
"client_request_body": "1 KB",
"provider_request_body": "4 KB",
@@ -816,6 +828,10 @@ mod tests {
"provider_request_body_base64_bytes": 512,
"provider_response_body_base64_bytes": 1024,
"client_response_body_base64_bytes": 2048,
"end_to_end_time_ms": 10626,
"end_to_end_first_byte_time_ms": 10120,
"transport_error": true,
"transport_error_type": "connect_timeout",
"body_size": {
"client_request_body": "1 KB",
"provider_request_body": "4 KB",
@@ -965,6 +981,10 @@ mod tests {
"client_requested_stream": false,
"upstream_is_stream": true,
"api_key_is_standalone": true,
"end_to_end_time_ms": 10626,
"end_to_end_first_byte_time_ms": 10120,
"transport_error": true,
"transport_error_type": "connect_timeout",
"provider_id": "provider-1",
"model_id": "model-1",
"global_model_id": "global-model-1",
@@ -998,6 +1018,10 @@ mod tests {
"client_requested_stream": false,
"upstream_is_stream": true,
"api_key_is_standalone": true,
"end_to_end_time_ms": 10626,
"end_to_end_first_byte_time_ms": 10120,
"transport_error": true,
"transport_error_type": "connect_timeout",
"model_id": "model-1",
"global_model_id": "global-model-1",
"global_model_name": "gpt-5",
@@ -1142,8 +1166,6 @@ mod tests {
apply_usage_body_capture_policy_to_event(
UsageBodyCapturePolicy {
record_level: UsageRequestRecordLevel::Basic,
max_request_body_bytes: Some(1024),
max_response_body_bytes: Some(1024),
},
&mut event,
);
+2 -6
View File
@@ -44,22 +44,18 @@ pub enum UsageRequestRecordLevel {
Full,
}
pub const DEFAULT_USAGE_REQUEST_BODY_CAPTURE_LIMIT_BYTES: usize = 5 * 1024 * 1024;
pub const DEFAULT_USAGE_RESPONSE_BODY_CAPTURE_LIMIT_BYTES: usize = 5 * 1024 * 1024;
pub const DEFAULT_USAGE_REQUEST_BODY_CAPTURE_LIMIT_BYTES: usize = usize::MAX;
pub const DEFAULT_USAGE_RESPONSE_BODY_CAPTURE_LIMIT_BYTES: usize = usize::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UsageBodyCapturePolicy {
pub record_level: UsageRequestRecordLevel,
pub max_request_body_bytes: Option<usize>,
pub max_response_body_bytes: Option<usize>,
}
impl Default for UsageBodyCapturePolicy {
fn default() -> Self {
Self {
record_level: UsageRequestRecordLevel::Full,
max_request_body_bytes: Some(DEFAULT_USAGE_REQUEST_BODY_CAPTURE_LIMIT_BYTES),
max_response_body_bytes: Some(DEFAULT_USAGE_RESPONSE_BODY_CAPTURE_LIMIT_BYTES),
}
}
}