修复抠图失败审计元数据
区分抠图传输失败与上游 HTTP 状态 补齐 BgFilter 与阿里云抠图失败 latencyMs 同步阿里云抠图配置与角色动作背景色文档口径
This commit is contained in:
@@ -32,6 +32,7 @@ pub(crate) async fn segment_image_with_aliyun_matting(
|
||||
.map_err(|error| {
|
||||
let message = error.message();
|
||||
let timeout = aliyun_matting_error_is_timeout(message);
|
||||
let upstream_status = aliyun_matting_error_http_status(message);
|
||||
let status = if timeout {
|
||||
StatusCode::GATEWAY_TIMEOUT
|
||||
} else {
|
||||
@@ -41,7 +42,9 @@ pub(crate) async fn segment_image_with_aliyun_matting(
|
||||
"provider": "aliyun-matting",
|
||||
"message": message,
|
||||
"timeout": timeout,
|
||||
"upstreamStatus": aliyun_matting_error_http_status(message),
|
||||
"transport": aliyun_matting_error_is_transport(message),
|
||||
"upstreamStatus": upstream_status,
|
||||
"latencyMs": started_at.elapsed().as_millis() as u64,
|
||||
"rawExcerpt": message.chars().take(500).collect::<String>(),
|
||||
}))
|
||||
})?;
|
||||
@@ -74,6 +77,10 @@ fn aliyun_matting_error_http_status(message: &str) -> Option<u16> {
|
||||
digits.parse().ok()
|
||||
}
|
||||
|
||||
fn aliyun_matting_error_is_transport(message: &str) -> bool {
|
||||
aliyun_matting_error_http_status(message).is_none()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -105,4 +112,14 @@ mod tests {
|
||||
"通用抠图接口返回失败(HTTP 400,Code=InvalidImage):bad image"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aliyun_matting_error_transport_requires_missing_http_status() {
|
||||
assert!(aliyun_matting_error_is_transport(
|
||||
"通用抠图请求失败:dns error"
|
||||
));
|
||||
assert!(!aliyun_matting_error_is_transport(
|
||||
"通用抠图接口返回失败(HTTP 429,Code=Throttled):QPS exceeded"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2247,6 +2247,7 @@ async fn remove_editor_character_animation_frame_backgrounds(
|
||||
"aliyun_segment",
|
||||
crate::external_api_audit::matting_failure_audit_status_code(&error),
|
||||
crate::external_api_audit::matting_failure_audit_timeout(&error),
|
||||
crate::external_api_audit::matting_failure_audit_latency_ms(&error),
|
||||
error.message().to_string(),
|
||||
crate::external_api_audit::matting_failure_audit_raw_excerpt(&error)
|
||||
.or_else(|| Some(format!("frame_index={frame_index}"))),
|
||||
|
||||
@@ -2268,6 +2268,7 @@ async fn remove_editor_generated_screen_background_with_bgfilter(
|
||||
"bgfilter_segment",
|
||||
crate::external_api_audit::matting_failure_audit_status_code(&error),
|
||||
crate::external_api_audit::matting_failure_audit_timeout(&error),
|
||||
crate::external_api_audit::matting_failure_audit_latency_ms(&error),
|
||||
error.message().to_string(),
|
||||
crate::external_api_audit::matting_failure_audit_raw_excerpt(&error),
|
||||
)
|
||||
@@ -2297,6 +2298,7 @@ async fn remove_editor_generated_screen_background_with_bgfilter(
|
||||
"aliyun_segment",
|
||||
crate::external_api_audit::matting_failure_audit_status_code(&error),
|
||||
crate::external_api_audit::matting_failure_audit_timeout(&error),
|
||||
crate::external_api_audit::matting_failure_audit_latency_ms(&error),
|
||||
error.message().to_string(),
|
||||
crate::external_api_audit::matting_failure_audit_raw_excerpt(&error),
|
||||
)
|
||||
@@ -2419,14 +2421,15 @@ async fn request_editor_generated_screen_background_with_bgfilter(
|
||||
request = request.header("X-Genarrative-Image-Token", token);
|
||||
}
|
||||
let response = request.send().await.map_err(|error| {
|
||||
let latency_ms = request_started_at.elapsed().as_millis() as u64;
|
||||
tracing::warn!(
|
||||
%call_id,
|
||||
elapsed_ms = request_started_at.elapsed().as_millis() as u64,
|
||||
elapsed_ms = latency_ms,
|
||||
timeout = error.is_timeout(),
|
||||
error = %error,
|
||||
"editor_bgfilter_request_failed"
|
||||
);
|
||||
map_editor_bgfilter_error(error)
|
||||
map_editor_bgfilter_error(error, latency_ms)
|
||||
})?;
|
||||
let status = response.status();
|
||||
if !status.is_success() {
|
||||
@@ -2446,6 +2449,7 @@ async fn request_editor_generated_screen_background_with_bgfilter(
|
||||
"provider": "bgfilter",
|
||||
"message": "BgFilter 服务返回非成功状态",
|
||||
"upstreamStatus": status.as_u16(),
|
||||
"latencyMs": request_started_at.elapsed().as_millis() as u64,
|
||||
"upstreamMessage": message.chars().take(500).collect::<String>(),
|
||||
})),
|
||||
);
|
||||
@@ -2702,7 +2706,7 @@ fn map_editor_background_removal_error(error: reqwest::Error) -> AppError {
|
||||
}))
|
||||
}
|
||||
|
||||
fn map_editor_bgfilter_error(error: reqwest::Error) -> AppError {
|
||||
fn map_editor_bgfilter_error(error: reqwest::Error, latency_ms: u64) -> AppError {
|
||||
let status = if error.is_timeout() {
|
||||
StatusCode::GATEWAY_TIMEOUT
|
||||
} else {
|
||||
@@ -2712,6 +2716,8 @@ fn map_editor_bgfilter_error(error: reqwest::Error) -> AppError {
|
||||
"provider": "bgfilter",
|
||||
"message": format!("请求 BgFilter 服务失败:{error}"),
|
||||
"timeout": error.is_timeout(),
|
||||
"transport": true,
|
||||
"latencyMs": latency_ms,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ pub(crate) async fn record_matting_external_api_failure(
|
||||
failure_stage: &'static str,
|
||||
status_code: Option<u16>,
|
||||
timeout: bool,
|
||||
latency_ms: Option<u64>,
|
||||
error_message: String,
|
||||
raw_excerpt: Option<String>,
|
||||
) {
|
||||
@@ -171,6 +172,7 @@ pub(crate) async fn record_matting_external_api_failure(
|
||||
timeout,
|
||||
false,
|
||||
))
|
||||
.with_latency_ms(latency_ms)
|
||||
.with_raw_excerpt(raw_excerpt)
|
||||
.with_audit_context(context);
|
||||
record_external_api_failure(state, draft).await;
|
||||
@@ -183,7 +185,7 @@ pub(crate) fn matting_failure_audit_status_code(error: &AppError) -> Option<u16>
|
||||
.and_then(Value::as_u64)
|
||||
.and_then(|value| u16::try_from(value).ok())
|
||||
.or_else(|| {
|
||||
if matting_failure_audit_timeout(error) {
|
||||
if matting_failure_audit_is_transport(error) {
|
||||
None
|
||||
} else {
|
||||
Some(error.status_code().as_u16())
|
||||
@@ -191,6 +193,15 @@ pub(crate) fn matting_failure_audit_status_code(error: &AppError) -> Option<u16>
|
||||
})
|
||||
}
|
||||
|
||||
fn matting_failure_audit_is_transport(error: &AppError) -> bool {
|
||||
matting_failure_audit_timeout(error)
|
||||
|| error
|
||||
.details()
|
||||
.and_then(|details| details.get("transport"))
|
||||
.and_then(Value::as_bool)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(crate) fn matting_failure_audit_timeout(error: &AppError) -> bool {
|
||||
error
|
||||
.details()
|
||||
@@ -199,6 +210,13 @@ pub(crate) fn matting_failure_audit_timeout(error: &AppError) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(crate) fn matting_failure_audit_latency_ms(error: &AppError) -> Option<u64> {
|
||||
error
|
||||
.details()
|
||||
.and_then(|details| details.get("latencyMs"))
|
||||
.and_then(Value::as_u64)
|
||||
}
|
||||
|
||||
pub(crate) fn matting_failure_audit_raw_excerpt(error: &AppError) -> Option<String> {
|
||||
error
|
||||
.details()
|
||||
@@ -541,6 +559,7 @@ mod tests {
|
||||
"message": "BgFilter 服务返回非成功状态",
|
||||
"upstreamStatus": 429,
|
||||
"upstreamMessage": "too many requests",
|
||||
"latencyMs": 345,
|
||||
}));
|
||||
|
||||
let status_code = matting_failure_audit_status_code(&error);
|
||||
@@ -556,6 +575,7 @@ mod tests {
|
||||
.with_status_code(status_code)
|
||||
.with_optional_status_class(Some(status_class(status_code)))
|
||||
.with_timeout(timeout)
|
||||
.with_latency_ms(matting_failure_audit_latency_ms(&error))
|
||||
.with_retryable(is_retryable_external_api_failure(
|
||||
status_code,
|
||||
timeout,
|
||||
@@ -568,6 +588,7 @@ mod tests {
|
||||
assert_eq!(tracking.metadata["statusClass"], "4xx");
|
||||
assert_eq!(tracking.metadata["timeout"], false);
|
||||
assert_eq!(tracking.metadata["retryable"], true);
|
||||
assert_eq!(tracking.metadata["latencyMs"], 345);
|
||||
assert_eq!(tracking.metadata["rawExcerpt"], "too many requests");
|
||||
}
|
||||
|
||||
@@ -604,4 +625,42 @@ mod tests {
|
||||
assert_eq!(tracking.metadata["timeout"], true);
|
||||
assert_eq!(tracking.metadata["retryable"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matting_failure_audit_keeps_non_timeout_transport_classification() {
|
||||
let error = AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": "bgfilter",
|
||||
"message": "请求 BgFilter 服务失败:dns error",
|
||||
"timeout": false,
|
||||
"transport": true,
|
||||
"latencyMs": 67,
|
||||
}));
|
||||
|
||||
let status_code = matting_failure_audit_status_code(&error);
|
||||
let timeout = matting_failure_audit_timeout(&error);
|
||||
let tracking = build_external_api_failure_tracking_draft(
|
||||
&ExternalApiFailureDraft::new(
|
||||
"bgfilter",
|
||||
"https://bgfilter.example/remove-background",
|
||||
"editor-screen-background-removal",
|
||||
"bgfilter_segment",
|
||||
error.message(),
|
||||
)
|
||||
.with_status_code(status_code)
|
||||
.with_optional_status_class(Some(status_class(status_code)))
|
||||
.with_timeout(timeout)
|
||||
.with_latency_ms(matting_failure_audit_latency_ms(&error))
|
||||
.with_retryable(is_retryable_external_api_failure(
|
||||
status_code,
|
||||
timeout,
|
||||
false,
|
||||
)),
|
||||
);
|
||||
|
||||
assert_eq!(tracking.metadata["statusCode"], Value::Null);
|
||||
assert_eq!(tracking.metadata["statusClass"], "transport");
|
||||
assert_eq!(tracking.metadata["timeout"], false);
|
||||
assert_eq!(tracking.metadata["retryable"], false);
|
||||
assert_eq!(tracking.metadata["latencyMs"], 67);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user