|
@@ -417,6 +417,126 @@ async fn log_api_call_error(
|
|
|
steam_account, api_name, method, url, response_status.as_u16(), elapsed_ms
|
|
steam_account, api_name, method, url, response_status.as_u16(), elapsed_ms
|
|
|
);
|
|
);
|
|
|
error!("{}", log_msg);
|
|
error!("{}", log_msg);
|
|
|
|
|
+
|
|
|
|
|
+ // 同时写入 error 文件(包含完整详细信息)
|
|
|
|
|
+ log_api_error_to_error_file(
|
|
|
|
|
+ sweep_file,
|
|
|
|
|
+ request_id_fallback,
|
|
|
|
|
+ steam_account,
|
|
|
|
|
+ api_name,
|
|
|
|
|
+ method,
|
|
|
|
|
+ url,
|
|
|
|
|
+ headers,
|
|
|
|
|
+ form_params,
|
|
|
|
|
+ Some(response_status),
|
|
|
|
|
+ Some(response_body),
|
|
|
|
|
+ None,
|
|
|
|
|
+ elapsed_ms,
|
|
|
|
|
+ ).await;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/// 将 Steam API 调用错误详细记录到 {sweep_request_id}-error.txt 文件
|
|
|
|
|
+/// 包含:时间戳、账号、API名称、URL、请求头、请求体、响应状态、响应体、耗时
|
|
|
|
|
+/// 注意:只记录失败/错误的调用,成功的不记录
|
|
|
|
|
+async fn log_api_error_to_error_file(
|
|
|
|
|
+ sweep_file: Option<&SweepShared>,
|
|
|
|
|
+ request_id_fallback: i64,
|
|
|
|
|
+ steam_account: &str,
|
|
|
|
|
+ api_name: &str,
|
|
|
|
|
+ method: &str,
|
|
|
|
|
+ url: &str,
|
|
|
|
|
+ headers: &HeaderMap,
|
|
|
|
|
+ form_params: Option<&[(String, String)]>,
|
|
|
|
|
+ response_status: Option<reqwest::StatusCode>,
|
|
|
|
|
+ response_body: Option<&str>,
|
|
|
|
|
+ error_msg: Option<&str>,
|
|
|
|
|
+ elapsed_ms: u64,
|
|
|
|
|
+) {
|
|
|
|
|
+ let rid = sweep_file.map(|s| s.sweep_request_id).unwrap_or(request_id_fallback);
|
|
|
|
|
+ if rid <= 0 {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 构建详细日志JSON
|
|
|
|
|
+ let headers_json = serde_json::to_value(headers.iter().map(|(k, v)| {
|
|
|
|
|
+ (k.as_str().to_string(), v.to_str().unwrap_or("").to_string())
|
|
|
|
|
+ }).collect::<HashMap<_, _>>()).unwrap_or(Value::Null);
|
|
|
|
|
+
|
|
|
|
|
+ let form_json: Value = match form_params {
|
|
|
|
|
+ Some(form) => {
|
|
|
|
|
+ serde_json::to_value(form.iter().map(|(k, v)| (k.clone(), v.clone())).collect::<Vec<_>>()).unwrap_or(Value::Null)
|
|
|
|
|
+ }
|
|
|
|
|
+ None => Value::Null,
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ let status_str = response_status
|
|
|
|
|
+ .map(|s| s.as_u16().to_string())
|
|
|
|
|
+ .unwrap_or_else(|| "N/A".to_string());
|
|
|
|
|
+
|
|
|
|
|
+ let body_str = response_body
|
|
|
|
|
+ .map(|s| {
|
|
|
|
|
+ let trimmed = s.trim();
|
|
|
|
|
+ if trimmed.is_empty() {
|
|
|
|
|
+ "(空响应)".to_string()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ trimmed.to_string()
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ .unwrap_or_else(|| "(无响应体)".to_string());
|
|
|
|
|
+
|
|
|
|
|
+ let err_str = error_msg.unwrap_or("").to_string();
|
|
|
|
|
+
|
|
|
|
|
+ // 生成 ISO 格式时间戳
|
|
|
|
|
+ let timestamp_iso = chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.3f").to_string();
|
|
|
|
|
+
|
|
|
|
|
+ // 构建完整的错误详情
|
|
|
|
|
+ let error_detail = json!({
|
|
|
|
|
+ "created_at": sweep_api_ts(),
|
|
|
|
|
+ "timestamp_iso": timestamp_iso,
|
|
|
|
|
+ "sweep_request_id": rid,
|
|
|
|
|
+ "steam_account": steam_account,
|
|
|
|
|
+ "api_name": api_name,
|
|
|
|
|
+ "method": method,
|
|
|
|
|
+ "url": url,
|
|
|
|
|
+ "request_headers": headers_json,
|
|
|
|
|
+ "request_body": form_json,
|
|
|
|
|
+ "response_status": status_str,
|
|
|
|
|
+ "response_body": body_str,
|
|
|
|
|
+ "error_message": err_str,
|
|
|
|
|
+ "elapsed_ms": elapsed_ms,
|
|
|
|
|
+ "summary": format!(
|
|
|
|
|
+ "[API-ERROR] {} {} -> {} ({}ms) | account={} | {}",
|
|
|
|
|
+ method,
|
|
|
|
|
+ api_name,
|
|
|
|
|
+ status_str,
|
|
|
|
|
+ elapsed_ms,
|
|
|
|
|
+ steam_account,
|
|
|
|
|
+ if err_str.is_empty() { "OK".to_string() } else { format!("ERR: {}", err_str) }
|
|
|
|
|
+ ),
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 获取 steam-msg 目录并写入 error 文件
|
|
|
|
|
+ if let Ok(steam_msg_dir) = get_steam_msg_dir() {
|
|
|
|
|
+ let error_file_path = steam_msg_dir.join(format!("{}-error.txt", rid));
|
|
|
|
|
+
|
|
|
|
|
+ // 构建带分隔符的日志条目
|
|
|
|
|
+ let log_line = serde_json::to_string(&error_detail).unwrap_or_else(|_| "{}".to_string());
|
|
|
|
|
+
|
|
|
|
|
+ // 追加写入 error 文件
|
|
|
|
|
+ if let Err(e) = std::fs::OpenOptions::new()
|
|
|
|
|
+ .create(true)
|
|
|
|
|
+ .append(true)
|
|
|
|
|
+ .open(&error_file_path)
|
|
|
|
|
+ .and_then(|mut f| {
|
|
|
|
|
+ use std::io::Write;
|
|
|
|
|
+ writeln!(f, "{}", log_line)
|
|
|
|
|
+ })
|
|
|
|
|
+ {
|
|
|
|
|
+ error!("[ERROR-LOG] 写入 error 文件失败: {}", e);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ info!("[ERROR-LOG] 已记录错误到 error 文件: {}", error_file_path.display());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// 扫货文件日志中的饰品维度前缀(与产品文案一致)。
|
|
/// 扫货文件日志中的饰品维度前缀(与产品文案一致)。
|
|
@@ -1236,6 +1356,22 @@ async fn http_get_steam(
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 同时写入 error 文件(包含完整详细信息)
|
|
|
|
|
+ log_api_error_to_error_file(
|
|
|
|
|
+ sweep_file,
|
|
|
|
|
+ rid,
|
|
|
|
|
+ account_name,
|
|
|
|
|
+ label,
|
|
|
|
|
+ "GET",
|
|
|
|
|
+ url,
|
|
|
|
|
+ &headers_for_log,
|
|
|
|
|
+ None,
|
|
|
|
|
+ None,
|
|
|
|
|
+ None,
|
|
|
|
|
+ Some(&last_err),
|
|
|
|
|
+ elapsed_ms,
|
|
|
|
|
+ ).await;
|
|
|
|
|
+
|
|
|
let need_retry = attempt < retry_times
|
|
let need_retry = attempt < retry_times
|
|
|
&& is_proxy_related_failure(None, Some(&last_err), None);
|
|
&& is_proxy_related_failure(None, Some(&last_err), None);
|
|
|
if !need_retry {
|
|
if !need_retry {
|
|
@@ -1442,6 +1578,22 @@ async fn http_post_form_steam(
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 同时写入 error 文件(包含完整详细信息)
|
|
|
|
|
+ log_api_error_to_error_file(
|
|
|
|
|
+ sweep_file,
|
|
|
|
|
+ rid,
|
|
|
|
|
+ account_name,
|
|
|
|
|
+ label,
|
|
|
|
|
+ "POST",
|
|
|
|
|
+ url,
|
|
|
|
|
+ &headers_for_log,
|
|
|
|
|
+ Some(form),
|
|
|
|
|
+ None,
|
|
|
|
|
+ None,
|
|
|
|
|
+ Some(&last_err),
|
|
|
|
|
+ elapsed_ms,
|
|
|
|
|
+ ).await;
|
|
|
|
|
+
|
|
|
let need_retry = attempt < retry_times;
|
|
let need_retry = attempt < retry_times;
|
|
|
if !need_retry {
|
|
if !need_retry {
|
|
|
return Err(last_err);
|
|
return Err(last_err);
|