From 72faf0433a56813f4c57ce3e6189b1d1fad8c3cf Mon Sep 17 00:00:00 2001 From: koko Date: Thu, 30 Apr 2026 21:43:15 +0800 Subject: [PATCH] Harden patrol delete logging --- app/amazon/patrol_delete.py | 65 +++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/app/amazon/patrol_delete.py b/app/amazon/patrol_delete.py index 59c27b3..7560373 100644 --- a/app/amazon/patrol_delete.py +++ b/app/amazon/patrol_delete.py @@ -1,5 +1,6 @@ import json import re +import sys import time from copy import deepcopy from datetime import datetime @@ -38,6 +39,15 @@ def _new_trace_id(prefix: str) -> str: return f"{prefix}-{int(time.time() * 1000) % 1000000:06d}" +def _safe_log_text(value: Any, limit: int = 3000) -> str: + text = "" if value is None else str(value) + text = re.sub(r"[\x00-\x08\x0b\x0c\x0e-\x1f]", " ", text) + if len(text) > limit: + text = text[: limit - 3] + "..." + encoding = getattr(getattr(sys, "stderr", None), "encoding", None) or "utf-8" + return text.encode(encoding, errors="backslashreplace").decode(encoding, errors="replace") + + def _load_patrol_delete_script(name: str) -> str: return (SCRIPT_DIR / name).read_text(encoding="utf-8") @@ -207,7 +217,10 @@ class InventoryManage(AmamzonBase): rows = self._wait_listing_status_section_rows() parsed_rows = self._parse_listing_status_section_rows(rows) if not parsed_rows: - logger.info(f"trace_id={trace_id} 商品状态统计读取为空: raw_rows={json.dumps(rows, ensure_ascii=False)}") + logger.info( + f"trace_id={trace_id} 商品状态统计读取为空: " + f"raw_rows={_safe_log_text(json.dumps(rows, ensure_ascii=False))}" + ) raise RuntimeError(f"未读取到商品状态统计标签: {rows}") logger.info( f"trace_id={trace_id} 商品状态统计读取完成: " @@ -356,13 +369,16 @@ class InventoryManage(AmamzonBase): if not option_rows: logger.info( f"trace_id={trace_id} 商品状态下拉选项为空: " - f"open_result={json.dumps(open_result, ensure_ascii=False)}" + f"open_result={_safe_log_text(json.dumps(open_result, ensure_ascii=False))}" ) raise RuntimeError(f"商品状态下拉框已打开但未读取到选项: {open_result}") result = self._run_js(LISTING_STATUS_SELECT_OPTION_SCRIPT, {"status": normalized_status}) or {} if not result.get("ok"): - logger.info(f"trace_id={trace_id} 商品状态选项未命中: status={normalized_status}, result={result}") + logger.info( + f"trace_id={trace_id} 商品状态选项未命中: " + f"status={normalized_status}, result={_safe_log_text(result)}" + ) raise RuntimeError(f"未找到商品状态选项[{normalized_status}]: {result}") time.sleep(LISTING_STATUS_SELECT_DELAY) self._wait_inventory_loader() @@ -413,7 +429,7 @@ class InventoryManage(AmamzonBase): target = self._summarize_inventory_row(row) select_result = self._run_js(INVENTORY_SELECT_ALL_SCRIPT) or {} if not select_result.get("ok"): - logger.info(f"trace_id={trace_id} 全选商品失败: status={status}, result={select_result}") + logger.info(f"trace_id={trace_id} 全选商品失败: status={status}, result={_safe_log_text(select_result)}") raise RuntimeError(f"状态[{status}]全选商品失败: {select_result}") logger.info( f"trace_id={trace_id} 当前筛选结果已全选: status={status}, " @@ -427,21 +443,27 @@ class InventoryManage(AmamzonBase): if delete_result.get("ok"): break if delete_result.get("menuOpened"): - logger.info(f"trace_id={trace_id} 状态[{status}]已点击选择组操作,等待删除菜单展开: {delete_result}") + logger.info( + f"trace_id={trace_id} 状态[{status}]已点击选择组操作,等待删除菜单展开: " + f"{_safe_log_text(delete_result)}" + ) time.sleep(1) continue - logger.info(f"trace_id={trace_id} 状态[{status}]第{attempt + 1}次点击批量删除未命中: {delete_result}") + logger.info( + f"trace_id={trace_id} 状态[{status}]第{attempt + 1}次点击批量删除未命中: " + f"{_safe_log_text(delete_result)}" + ) if not delete_result.get("ok"): - logger.info(f"trace_id={trace_id} 点击批量删除失败: status={status}, result={delete_result}") + logger.info(f"trace_id={trace_id} 点击批量删除失败: status={status}, result={_safe_log_text(delete_result)}") raise RuntimeError(f"状态[{status}]点击批量删除失败: {delete_result}") - logger.info(f"trace_id={trace_id} 批量删除按钮点击成功: status={status}, result={delete_result}") + logger.info(f"trace_id={trace_id} 批量删除按钮点击成功: status={status}, result={_safe_log_text(delete_result)}") self._confirm_delete_modal(status, trace_id=trace_id) success_message = self._wait_delete_success_message(status, trace_id=trace_id) deleted_count = self._extract_delete_success_count(success_message) or int(select_result.get("rowCount") or 1) logger.info( f"trace_id={trace_id} 批量删除确认完成: status={status}, " - f"deleted_count={deleted_count}, success_message={success_message}" + f"deleted_count={deleted_count}, success_message={_safe_log_text(success_message)}" ) return { "deletedCount": deleted_count, @@ -472,10 +494,13 @@ class InventoryManage(AmamzonBase): trace_id = trace_id or _new_trace_id("modal") js_result = self._click_confirm_delete_modal_with_js() if js_result.get("ok"): - logger.info(f"trace_id={trace_id} 已通过 JS 确认删除弹窗: status={status}, result={js_result}") + logger.info(f"trace_id={trace_id} 已通过 JS 确认删除弹窗: status={status}, result={_safe_log_text(js_result)}") return - logger.info(f"trace_id={trace_id} JS 确认删除弹窗未命中,尝试驱动点击: status={status}, result={js_result}") + logger.info( + f"trace_id={trace_id} JS 确认删除弹窗未命中,尝试驱动点击: " + f"status={status}, result={_safe_log_text(js_result)}" + ) confirm_button = self.tab.ele( 'xpath://kat-modal[@data-testid="action-modal"]//kat-button[@variant="primary"]', timeout=5, @@ -507,10 +532,13 @@ class InventoryManage(AmamzonBase): if text not in seen_messages: seen_messages.append(text) if self._is_delete_success_message(text): - logger.info(f"trace_id={trace_id} 读取到删除成功提示: status={status}, message={text}") + logger.info(f"trace_id={trace_id} 读取到删除成功提示: status={status}, message={_safe_log_text(text)}") return text time.sleep(0.3) - logger.info(f"trace_id={trace_id} 删除后未读取到成功提示: status={status}, seen_messages={seen_messages}") + logger.info( + f"trace_id={trace_id} 删除后未读取到成功提示: " + f"status={status}, seen_messages={_safe_log_text(seen_messages)}" + ) raise RuntimeError("删除后未读取到成功提示") def _collect_delete_success_messages(self) -> List[str]: @@ -1260,7 +1288,7 @@ class PatrolDeleteTask: def log(self, message: str, level: str = "INFO"): level_name = str(level or "INFO").upper() - logger.log(level_name, f"[ProductTask] {message}") + logger.log(level_name, f"[PatrolDeleteTask] {_safe_log_text(message)}") def process_task(self, task_data: dict): """处理巡店删除任务主入口。""" @@ -1658,7 +1686,7 @@ class PatrolDeleteTask: try: self.log(f"trace_id={trace_id} 尝试回传巡店删除结果 (第 {retry + 1}/{max_retries} 次)") self.log(f"trace_id={trace_id} 回传URL: {url}") - self.log(f"trace_id={trace_id} 回传数据: {payload}") + self.log(f"trace_id={trace_id} 回传数据: {_safe_log_text(payload)}") response = requests.post( url, json=payload, @@ -1666,7 +1694,7 @@ class PatrolDeleteTask: timeout=30, verify=False, ) - self.log(f"trace_id={trace_id} 回传结果: {response.text}") + self.log(f"trace_id={trace_id} 回传结果: {_safe_log_text(response.text)}") try: response_data = response.json() except (AttributeError, ValueError): @@ -1674,13 +1702,14 @@ class PatrolDeleteTask: api_success = not (isinstance(response_data, dict) and response_data.get("success") is False) self.log( f"trace_id={trace_id} 巡店删除结果回传响应: status_code={response.status_code}, " - f"api_success={api_success}, response={response.text}" + f"api_success={api_success}, response={_safe_log_text(response.text)}" ) if response.status_code == 200 and api_success: self.log(f"trace_id={trace_id} 巡店删除结果回传成功: {shop_name}") return self.log( - f"trace_id={trace_id} 巡店删除结果回传失败,状态码: {response.status_code}, 响应: {response.text}", + f"trace_id={trace_id} 巡店删除结果回传失败,状态码: {response.status_code}, " + f"响应: {_safe_log_text(response.text)}", "WARNING", ) except Exception as exc: