'日志系统迭代'

This commit is contained in:
zelong 2026-03-10 20:53:32 +08:00
parent 403b8fee0d
commit c5f2c64e82
10 changed files with 241 additions and 34 deletions

View File

@ -9,16 +9,16 @@ workspace_id = 58335167
# 支持配置多个docid用逗号分隔例如 # 支持配置多个docid用逗号分隔例如
# docid = doc1,doc2,doc3 # docid = doc1,doc2,doc3
# 同步时会依次对所有表格进行同步 # 同步时会依次对所有表格进行同步
docid = dcOsT3czWy0YEDg38vlDqwVCTjv0kzwC_GU2XmT9wSZctQ0ZJQUAV7vMQ3ljZx-n_NqxzEEYG2DiLAvNdNsHJwgQ,dcHWzWyaHpZNQwUkZzgH5Kfyx9cMvQzVjZIapajGDuXqjS4nEe0LQqOojBL8s3rlwghw4deOgVnbOqHLoxcKzaHg # docid = dcOsT3czWy0YEDg38vlDqwVCTjv0kzwC_GU2XmT9wSZctQ0ZJQUAV7vMQ3ljZx-n_NqxzEEYG2DiLAvNdNsHJwgQ,dcHWzWyaHpZNQwUkZzgH5Kfyx9cMvQzVjZIapajGDuXqjS4nEe0LQqOojBL8s3rlwghw4deOgVnbOqHLoxcKzaHg
#docid =dc2Q5Kb0T4zerbo4_ag0MMcXHCusIaFJX5fO6_8n-l_yV-bn5brZSi1kNw3kjme-qIs0LvPKbC5GDEEPaZ1BGlvA docid =dc2Q5Kb0T4zerbo4_ag0MMcXHCusIaFJX5fO6_8n-l_yV-bn5brZSi1kNw3kjme-qIs0LvPKbC5GDEEPaZ1BGlvA
[Schedule] [Schedule]
# 同步频率(分钟) # 同步频率(分钟)
sync_interval = 30 sync_interval = 1
[wework] [wework]
# 企业微信应用ID # 企业微信应用ID
agentid = 1000615 agentid = 1000615
# 接收人列表用户ID多个用|分隔,@all表示全部成员 # 接收人列表用户ID多个用|分隔,@all表示全部成员
# receivers = 046364 receivers = 046364
receivers = 040005 # receivers = 114514

View File

@ -1,4 +1,4 @@
{ {
"access_token": "GQKX61Nh9c5A4Mp0FDOGy4nZgnFok2gefTA0_X4Y5PEL7NkpD9UHWwji3lWkZLsHMJf3dpbJ_l-NdichZ5qSZuPhF7kJNU47Blf2yLQRqFctmXMU6m1cWU80iLiY0vrX2EPzvldaHMR-al3HgKK6PUSU9T2a5Xp-lCjh9StPzEnQJUnwickV4PiPegLLGcH5F6jcM-9pHztkJ6pSV6bfP5QFFATAldmj-71Occib9V0", "access_token": "720bS457oj9c3NehV8Be0YZoqDTdzQhlk1MppoMMdn078G_ZDiJ5BPzbLw5nckIpV6rQLGBYeNc-GkKJgFNMKbhqa52JUDj6o0czz3q3WGAVGEH4z-GSSPDrHft38yjG_kjnk1Aa_LayL6dG_Ndy8POYhNKxyjiAz_veePxnzpWzhwRrQA5YsL2ufrAD12sjL6zfBONkDrLweLjlXPWVK3J9VJThtBMN0K3xzFI0Ku0",
"fetch_time": 1772269143.6670134 "fetch_time": 1773146433.440257
} }

View File

@ -54,20 +54,20 @@
- **接口(对内)**`create_bug``get_bug``upload_attachment` 等。 - **接口(对内)**`create_bug``get_bug``upload_attachment` 等。
- `src/token_manager.py` - `src/token_manager.py`
- **职责**:企业微信 `access_token` 获取与缓存。 - **职责**:企业微信 `access_token` 获取与缓存。
- **接口(对内)**`get_token()`。 - **接口(对内)**`__init__(cache_file_path=None, logger=None)`、`get_token()`。
- `src/wework_notifier.py` - `src/wework_notifier.py`
- **职责**:企业微信消息通知。 - **职责**:企业微信消息通知。
- **接口(对内)**`send_validation_failure_notification(...)`。 - **接口(对内)**`__init__(access_token, agentid, receivers, logger=None)`、`send_validation_failure_notification(...)`。
- `src/api_logger.py` - `src/api_logger.py`
- **职责**:现有日志记录器(后续将作为兼容层)。 - **职责**:现有日志记录器(后续将作为兼容层)。
## 2.2 任务二(`src2/` ## 2.2 任务二(`src2/`
- `src2/scheduler.py` - `src2/scheduler.py`
- **职责**:任务二调度入口,定时触发同步。 - **职责**:任务二调度入口,定时触发同步并记录每次同步边界与统计
- **关键依赖**`src2/sync_service.py` - **关键依赖**`src2/sync_service.py`
- `src2/sync_service.py` - `src2/sync_service.py`
- **职责**:任务二同步编排(读取、解析链接、查询 TAPD、回写、通知 - **职责**:任务二同步编排(读取、解析链接、查询 TAPD、回写、通知
- **接口(对内)**`sync_once()` - **接口(对内)**`sync_once()``run_once(...)`(无外层同步时自动兜底边界)
- `src2/smartsheet.py` - `src2/smartsheet.py`
- **职责**:任务二智能表格 API 适配层。 - **职责**:任务二智能表格 API 适配层。
- **关键点**:应固定写入 `logs2/` - **关键点**:应固定写入 `logs2/`
@ -76,18 +76,18 @@
- `src2/tapd_api.py` - `src2/tapd_api.py`
- **职责**:任务二 TAPD Story 查询与状态映射。 - **职责**:任务二 TAPD Story 查询与状态映射。
- `src2/notifier.py` - `src2/notifier.py`
- **职责**:任务二失败通知封装(当前复用任务一通知器,存在串目录风险)。 - **职责**:任务二失败通知封装(复用任务一通知器并显式注入任务二 logger避免串目录)。
- `src2/logger.py` - `src2/logger.py`
- **职责**:任务二日志实例入口(后续接入统一内核)。 - **职责**:任务二日志实例入口(固定写入 `logs2/`,接入统一内核兼容层)。
--- ---
## 3. 现存问题与待改造点 ## 3. 现存问题与待改造点阶段3后
- **串目录问题**:任务二复用 `TokenManager``WeWorkNotifier` 可能写入 `logs/` - **串目录问题(生产链路)**:已修复,`src2` 生产入口统一显式注入任务二 logger
- **双记录矛盾**:同一次请求在部分分支可能出现先成功后失败两条记录 - **双记录矛盾(任务一)**:已修复;任务二当前未发现同请求双写路径
- **写入稳定性问题**现有按 JSON 数组拼接的策略会造成结构损坏风险 - **写入稳定性问题**已由统一 `jsonl` 事件流替代旧数组拼接策略
- **同步边界缺失**:缺少标准化 `start_sync` / `end_sync` 分隔与统计记录 - **阶段4待办**:日志查看工具尚未完成 `jsonl + sync_id` 体验优化
--- ---
@ -95,7 +95,7 @@
- 阶段1新增全局日志内核模块定义统一接口。已完成 - 阶段1新增全局日志内核模块定义统一接口。已完成
- 阶段2`src/api_logger.py` 改造成兼容层,保证旧调用可用。(已完成) - 阶段2`src/api_logger.py` 改造成兼容层,保证旧调用可用。(已完成)
- 阶段3`src2/logger.py` 与任务二编排层切换到统一内核,修复串目录。 - 阶段3`src2/logger.py` 与任务二编排层切换到统一内核,修复串目录。(已完成)
- 阶段4更新查看工具与文档支持 `jsonl + sync_id` - 阶段4更新查看工具与文档支持 `jsonl + sync_id`
--- ---
@ -115,3 +115,8 @@
- `src/scheduler.py``job/sync_job` 已接入同步边界与统计事件。 - `src/scheduler.py``job/sync_job` 已接入同步边界与统计事件。
- `src/main.py``run_once` 已接入手动兜底同步边界。 - `src/main.py``run_once` 已接入手动兜底同步边界。
- `src/smartsheet.py` 已消除同请求双记录矛盾。 - `src/smartsheet.py` 已消除同请求双记录矛盾。
### 2026-03-10更新3
- 标记阶段3完成任务二生产链路已接入同步边界与统计收尾。
- TokenManager、WeWorkNotifier 已支持 logger 注入,用于任务二目录隔离。
- src2/notifier.py、src2/sync_service.py、src2/main.py、src2/scheduler.py 已完成 logs2 链路闭环。

View File

@ -143,3 +143,46 @@
- **可回滚点**:可按文件粒度回滚(`src/api_logger.py``src/scheduler.py` 为关键点)。 - **可回滚点**:可按文件粒度回滚(`src/api_logger.py``src/scheduler.py` 为关键点)。
- **关联文档**`docs/日志系统重构实施方案.md` - **关联文档**`docs/日志系统重构实施方案.md`
- **备注**:下一阶段优先处理任务二串目录与通知链路复用问题。 - **备注**:下一阶段优先处理任务二串目录与通知链路复用问题。
## 阶段3接入任务二src2
- **阶段名称**:日志系统重构 - 阶段3
- **日期**2026-03-10
- **负责人**Codex
- **目标**:修复任务二串目录问题,补齐任务二同步分隔与统计收尾,确保生产链路日志只落 `logs2/`
### 变更清单
- **新增文件**:无
- **修改文件**
- `src/token_manager.py`
- `src/wework_notifier.py`
- `src2/scheduler.py`
- `src2/main.py`
- `src2/sync_service.py`
- `src2/notifier.py`
- `docs/全局迭代日志.md`
- `docs/全局框架文档.md`
- **删除文件**:无
### 关键改动说明
- **日志结构变更**:任务二调度链路与手动入口均补齐 `start_sync/end_sync_with_stats`,按 `sync_id` 分隔并写入统计。
- **接口/调用链变更**
- `TokenManager``WeWorkNotifier` 新增可选 `logger` 注入参数。
- `src2/scheduler.py``src2/main.py``src2/sync_service.py` 全部接入任务二 logger 边界控制。
- `src2/notifier.py` 调用通知器时显式注入 `get_task2_logger()`
- **兼容性说明**:旧调用不传 `logger` 仍保持任务一默认行为,生产链路通过显式注入实现目录隔离。
### 验收结果
- **通过项**
- 任务二生产链路(`src2/scheduler.py`)已具备每次同步开始/结束事件与统计写入。
- 任务二手动入口(`src2/main.py`)与 `run_once` 兜底路径已具备同步边界。
- 任务二 token 获取、通知链路、sync_service 自动取 token 全部使用任务二 logger不再串写 `logs/`
- `api_type` 已明确映射为 `smartsheet/tapd/wework` 三类语义。
- **未通过项**
- 未执行运行态联调(仅完成静态改造与代码级复核)。
- **遗留风险**
- `src2/test_*` 中仍有 `TokenManager()` 默认实例,属于测试路径,不影响生产链路。
### 回滚与追踪
- **可回滚点**:可按文件粒度回滚,优先关注 `src2/scheduler.py``src2/main.py``src2/sync_service.py`
- **关联文档**`docs/日志系统重构实施方案.md`
- **备注**阶段4建议优先补日志查看工具对 `jsonl + sync_id` 的检索能力。

View File

@ -26,12 +26,13 @@ class TokenManager:
# 提前刷新时间在token过期前5分钟就刷新 # 提前刷新时间在token过期前5分钟就刷新
REFRESH_BEFORE_EXPIRE = 300 REFRESH_BEFORE_EXPIRE = 300
def __init__(self, cache_file_path: Optional[str] = None): def __init__(self, cache_file_path: Optional[str] = None, logger=None):
""" """
初始化Token管理器 初始化Token管理器
Args: Args:
cache_file_path: token缓存文件路径如果为None则使用默认路径 cache_file_path: token缓存文件路径如果为None则使用默认路径
logger: 可选日志实例不传则使用默认任务一日志器
""" """
# 确定缓存文件路径 # 确定缓存文件路径
if cache_file_path is None: if cache_file_path is None:
@ -46,7 +47,7 @@ class TokenManager:
self.corpsecret = os.environ.get('CORPSECRET') self.corpsecret = os.environ.get('CORPSECRET')
# 初始化日志记录器 # 初始化日志记录器
self.logger = get_logger() self.logger = logger if logger is not None else get_logger()
def _validate_env_config(self): def _validate_env_config(self):
""" """

View File

@ -14,7 +14,7 @@ from src.api_logger import get_logger
class WeWorkNotifier: class WeWorkNotifier:
"""企业微信消息推送类""" """企业微信消息推送类"""
def __init__(self, access_token: str, agentid: str, receivers: str): def __init__(self, access_token: str, agentid: str, receivers: str, logger=None):
""" """
初始化企业微信消息推送器 初始化企业微信消息推送器
@ -22,12 +22,13 @@ class WeWorkNotifier:
access_token: 企业微信access_token access_token: 企业微信access_token
agentid: 应用ID agentid: 应用ID
receivers: 接收人列表用户ID多个用|分隔@all表示全部成员 receivers: 接收人列表用户ID多个用|分隔@all表示全部成员
logger: 可选日志实例不传则使用默认任务一日志器
""" """
self.access_token = access_token self.access_token = access_token
self.agentid = agentid self.agentid = agentid
self.receivers = receivers self.receivers = receivers
self.base_url = "https://qyapi.weixin.qq.com/cgi-bin" self.base_url = "https://qyapi.weixin.qq.com/cgi-bin"
self.logger = get_logger() self.logger = logger if logger is not None else get_logger()
def send_validation_failure_notification(self, invalid_records: List[Dict]) -> bool: def send_validation_failure_notification(self, invalid_records: List[Dict]) -> bool:
""" """

View File

@ -20,6 +20,7 @@ sys.path.insert(0, str(project_root))
from src.token_manager import TokenManager from src.token_manager import TokenManager
from src2.config import Task2ConfigManager from src2.config import Task2ConfigManager
from src2.sync_service import run_once from src2.sync_service import run_once
from src2.logger import get_task2_logger
def parse_arguments(): def parse_arguments():
@ -110,10 +111,22 @@ def main():
print(f"执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60) print("=" * 60)
logger = None
try: try:
# 解析命令行参数 # 解析命令行参数
args = parse_arguments() args = parse_arguments()
logger = get_task2_logger()
logger.start_sync(
trigger="task2_main_manual",
metadata={
"entry": "src2/main.py:main",
"test_mode": args.test,
"manual_token": bool(args.token),
},
)
# 初始化配置管理器 # 初始化配置管理器
print("\n正在加载配置...") print("\n正在加载配置...")
config_manager = Task2ConfigManager(config_path=args.config) config_manager = Task2ConfigManager(config_path=args.config)
@ -123,7 +136,7 @@ def main():
access_token = args.token access_token = args.token
if access_token is None: if access_token is None:
print("正在获取access_token...") print("正在获取access_token...")
token_manager = TokenManager() token_manager = TokenManager(logger=logger)
access_token = token_manager.get_token() access_token = token_manager.get_token()
print(f" ✓ access_token获取成功") print(f" ✓ access_token获取成功")
@ -138,19 +151,53 @@ def main():
# 打印结果摘要 # 打印结果摘要
print_result_summary(result) print_result_summary(result)
logger.end_sync_with_stats(
stats={
"docs_total": result.get("docs_total", 0),
"docs_success": result.get("docs_success", 0),
"docs_failed": result.get("docs_failed", 0),
"sheets_processed": result.get("sheets_processed", 0),
"sheets_skipped": result.get("sheets_skipped", 0),
"total_records": result.get("total_records", 0),
"records_with_link": result.get("records_with_link", 0),
"records_synced": result.get("records_synced", 0),
"records_updated": result.get("records_updated", 0),
"records_failed": result.get("records_failed", 0),
},
success=result.get("success", False),
error_message=result.get("error_message"),
extra={"source": "task2_main_manual"},
)
# 返回状态码 # 返回状态码
return 0 if result["success"] else 1 return 0 if result["success"] else 1
except KeyboardInterrupt: except KeyboardInterrupt:
print("\n\n用户中断执行") print("\n\n用户中断执行")
if logger and logger.get_active_sync_id():
logger.end_sync_with_stats(
stats={},
success=False,
error_message="KeyboardInterrupt",
extra={"source": "task2_main_manual"},
)
return 130 return 130
except Exception as e: except Exception as e:
print(f"\n✗ 执行失败: {e}") print(f"\n✗ 执行失败: {e}")
import traceback import traceback
traceback.print_exc() traceback.print_exc()
if logger and logger.get_active_sync_id():
logger.end_sync_with_stats(
stats={},
success=False,
error_message=str(e),
extra={
"source": "task2_main_manual",
"exception_type": type(e).__name__,
},
)
return 1 return 1
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())

View File

@ -13,6 +13,7 @@ project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root)) sys.path.insert(0, str(project_root))
from src.wework_notifier import WeWorkNotifier from src.wework_notifier import WeWorkNotifier
from src2.logger import get_task2_logger
def send_sync_failure_notification(access_token: str, agentid: str, def send_sync_failure_notification(access_token: str, agentid: str,
@ -40,7 +41,7 @@ def send_sync_failure_notification(access_token: str, agentid: str,
content = _build_sync_failure_message(failed_records) content = _build_sync_failure_message(failed_records)
# 使用任务一的推送器发送消息 # 使用任务一的推送器发送消息
notifier = WeWorkNotifier(access_token, agentid, receivers) notifier = WeWorkNotifier(access_token, agentid, receivers, logger=get_task2_logger())
return notifier._send_text_message(content) return notifier._send_text_message(content)

View File

@ -30,6 +30,7 @@ except ImportError:
from src.token_manager import TokenManager from src.token_manager import TokenManager
from src2.config import Task2ConfigManager from src2.config import Task2ConfigManager
from src2.sync_service import run_once from src2.sync_service import run_once
from src2.logger import get_task2_logger
class Task2Scheduler: class Task2Scheduler:
@ -82,7 +83,7 @@ class Task2Scheduler:
"""初始化TokenManager""" """初始化TokenManager"""
try: try:
print("正在初始化TokenManager...") print("正在初始化TokenManager...")
self.token_manager = TokenManager() self.token_manager = TokenManager(logger=get_task2_logger())
print("✓ TokenManager初始化成功") print("✓ TokenManager初始化成功")
except Exception as e: except Exception as e:
print(f"✗ TokenManager初始化失败: {e}") print(f"✗ TokenManager初始化失败: {e}")
@ -90,6 +91,30 @@ class Task2Scheduler:
def job(self): def job(self):
"""执行一次同步任务""" """执行一次同步任务"""
logger = get_task2_logger()
logger.start_sync(
trigger="task2_scheduler_job",
metadata={
"entry": "src2/scheduler.py:Task2Scheduler.job",
"verbose": self.verbose,
}
)
result = {
"success": False,
"docs_total": 0,
"docs_success": 0,
"docs_failed": 0,
"sheets_processed": 0,
"sheets_skipped": 0,
"total_records": 0,
"records_with_link": 0,
"records_synced": 0,
"records_updated": 0,
"records_failed": 0,
"error_message": None,
}
print("\n" + "=" * 80) print("\n" + "=" * 80)
print(f"开始执行同步任务 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"开始执行同步任务 - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 80) print("=" * 80)
@ -130,6 +155,24 @@ class Task2Scheduler:
print(f" 错误信息: {result.get('error_message', '未知错误')}") print(f" 错误信息: {result.get('error_message', '未知错误')}")
print("-" * 80) print("-" * 80)
logger.end_sync_with_stats(
stats={
"docs_total": result.get("docs_total", 0),
"docs_success": result.get("docs_success", 0),
"docs_failed": result.get("docs_failed", 0),
"sheets_processed": result.get("sheets_processed", 0),
"sheets_skipped": result.get("sheets_skipped", 0),
"total_records": result.get("total_records", 0),
"records_with_link": result.get("records_with_link", 0),
"records_synced": result.get("records_synced", 0),
"records_updated": result.get("records_updated", 0),
"records_failed": result.get("records_failed", 0),
},
success=result.get("success", False),
error_message=result.get("error_message"),
extra={"source": "task2_scheduler_job"},
)
except Exception as e: except Exception as e:
self.stats['total_runs'] += 1 self.stats['total_runs'] += 1
self.stats['failed_runs'] += 1 self.stats['failed_runs'] += 1
@ -146,6 +189,28 @@ class Task2Scheduler:
print("\n详细错误信息:") print("\n详细错误信息:")
traceback.print_exc() traceback.print_exc()
if logger.get_active_sync_id():
logger.end_sync_with_stats(
stats={
"docs_total": result.get("docs_total", 0),
"docs_success": result.get("docs_success", 0),
"docs_failed": result.get("docs_failed", 0),
"sheets_processed": result.get("sheets_processed", 0),
"sheets_skipped": result.get("sheets_skipped", 0),
"total_records": result.get("total_records", 0),
"records_with_link": result.get("records_with_link", 0),
"records_synced": result.get("records_synced", 0),
"records_updated": result.get("records_updated", 0),
"records_failed": result.get("records_failed", 0),
},
success=False,
error_message=str(e),
extra={
"source": "task2_scheduler_job",
"exception_type": type(e).__name__,
},
)
# 显示下次执行时间 # 显示下次执行时间
self._show_next_run_time() self._show_next_run_time()

View File

@ -66,7 +66,7 @@ class SyncService:
# 获取access_token # 获取access_token
if access_token is None: if access_token is None:
token_manager = TokenManager() token_manager = TokenManager(logger=self.logger)
self.access_token = token_manager.get_token() self.access_token = token_manager.get_token()
else: else:
self.access_token = access_token self.access_token = access_token
@ -465,7 +465,7 @@ class SyncService:
record_result["sync_status"] = "❌ 单号无效" record_result["sync_status"] = "❌ 单号无效"
# 记录链接解析失败日志 # 记录链接解析失败日志
self.logger.log_api_call( self.logger.log_api_call(
api_type="task2", api_type="smartsheet",
operation="link_parse_failure", operation="link_parse_failure",
request_data={ request_data={
"record_id": record_info["record_id"], "record_id": record_info["record_id"],
@ -526,7 +526,7 @@ class SyncService:
record_result["sync_status"] = "❌ 单号无效" record_result["sync_status"] = "❌ 单号无效"
# 记录TAPD查询失败日志 # 记录TAPD查询失败日志
self.logger.log_api_call( self.logger.log_api_call(
api_type="task2", api_type="tapd",
operation="sync_record_failure", operation="sync_record_failure",
request_data={ request_data={
"record_id": record_info["record_id"], "record_id": record_info["record_id"],
@ -543,7 +543,7 @@ class SyncService:
record_result["sync_status"] = "⚠️ 同步失败请联系PM" record_result["sync_status"] = "⚠️ 同步失败请联系PM"
# 记录TAPD查询失败日志 # 记录TAPD查询失败日志
self.logger.log_api_call( self.logger.log_api_call(
api_type="task2", api_type="tapd",
operation="sync_record_failure", operation="sync_record_failure",
request_data={ request_data={
"record_id": record_info["record_id"], "record_id": record_info["record_id"],
@ -685,7 +685,7 @@ class SyncService:
error_msg = str(e) error_msg = str(e)
# 记录持续同步失败日志 # 记录持续同步失败日志
self.logger.log_api_call( self.logger.log_api_call(
api_type="task2", api_type="tapd",
operation="continuous_sync_failure", operation="continuous_sync_failure",
request_data={ request_data={
"record_id": record_info["record_id"], "record_id": record_info["record_id"],
@ -731,7 +731,7 @@ class SyncService:
# 记录到日志文件 # 记录到日志文件
self.logger.log_api_call( self.logger.log_api_call(
api_type="task2", api_type="tapd",
operation="cache_statistics", operation="cache_statistics",
request_data={}, request_data={},
response_data=stats, response_data=stats,
@ -803,8 +803,52 @@ def run_once(config_manager: Task2ConfigManager = None,
access_token=access_token, access_token=access_token,
test_mode=test_mode test_mode=test_mode
) )
return service.sync_once()
logger = service.logger
started_sync_here = False
if not logger.get_active_sync_id():
logger.start_sync(
trigger="task2_run_once_manual",
metadata={
"entry": "src2/sync_service.py:run_once",
"test_mode": test_mode,
},
)
started_sync_here = True
try:
result = service.sync_once()
if started_sync_here:
logger.end_sync_with_stats(
stats={
"docs_total": result.get("docs_total", 0),
"docs_success": result.get("docs_success", 0),
"docs_failed": result.get("docs_failed", 0),
"sheets_processed": result.get("sheets_processed", 0),
"sheets_skipped": result.get("sheets_skipped", 0),
"total_records": result.get("total_records", 0),
"records_with_link": result.get("records_with_link", 0),
"records_synced": result.get("records_synced", 0),
"records_updated": result.get("records_updated", 0),
"records_failed": result.get("records_failed", 0),
},
success=result.get("success", False),
error_message=result.get("error_message"),
extra={"source": "task2_run_once_manual"},
)
return result
except Exception as e:
if started_sync_here and logger.get_active_sync_id():
logger.end_sync_with_stats(
stats={},
success=False,
error_message=str(e),
extra={
"source": "task2_run_once_manual",
"exception_type": type(e).__name__,
},
)
raise
if __name__ == "__main__": if __name__ == "__main__":
print("=== 任务二同步服务测试 ===\n") print("=== 任务二同步服务测试 ===\n")