Răsfoiți Sursa

Use logging for diagnostics in core modules

lingfengQAQ 4 luni în urmă
părinte
comite
5d48351acf

+ 7 - 6
.claude/scripts/data_modules/context_manager.py

@@ -8,6 +8,7 @@ from __future__ import annotations
 import json
 import re
 import sys
+import logging
 from pathlib import Path
 
 from runtime_compat import enable_windows_utf8_stdio
@@ -36,6 +37,9 @@ from .writing_guidance_builder import (
 )
 
 
+logger = logging.getLogger(__name__)
+
+
 class ContextManager:
     DEFAULT_TEMPLATE = CONTEXT_DEFAULT_TEMPLATE
     TEMPLATE_WEIGHTS = CONTEXT_TEMPLATE_WEIGHTS
@@ -486,10 +490,7 @@ class ContextManager:
                 )
             )
         except Exception as exc:
-            print(
-                f"[context_manager] failed to persist writing checklist score: {exc}",
-                file=sys.stderr,
-            )
+            logger.warning("failed to persist writing checklist score: %s", exc)
 
     def _resolve_context_stage(self, chapter: int) -> str:
         early = max(1, int(getattr(self.config, "context_dynamic_budget_early_chapter", 30)))
@@ -719,7 +720,7 @@ def main():
         try:
             manager.index_manager.log_tool_call("context_manager:build", True, chapter=args.chapter)
         except Exception as exc:
-            print(f"[context_manager] failed to log successful tool call: {exc}", file=sys.stderr)
+            logger.warning("failed to log successful tool call: %s", exc)
     except Exception as exc:
         print_error("CONTEXT_BUILD_FAILED", str(exc), suggestion="请检查项目结构与依赖文件")
         try:
@@ -727,7 +728,7 @@ def main():
                 "context_manager:build", False, error_code="CONTEXT_BUILD_FAILED", error_message=str(exc), chapter=args.chapter
             )
         except Exception as log_exc:
-            print(f"[context_manager] failed to log failed tool call: {log_exc}", file=sys.stderr)
+            logger.warning("failed to log failed tool call: %s", log_exc)
 
 
 if __name__ == "__main__":

+ 11 - 5
.claude/scripts/data_modules/index_entity_mixin.py

@@ -7,11 +7,14 @@ IndexEntityMixin extracted from IndexManager.
 from __future__ import annotations
 
 import json
-import sys
+import logging
 from datetime import datetime
 from typing import Any, Dict, List, Optional
 
 
+logger = logging.getLogger(__name__)
+
+
 class IndexEntityMixin:
     def upsert_entity(self, entity: EntityMeta, update_metadata: bool = False) -> bool:
         """
@@ -39,7 +42,10 @@ class IndexEntityMixin:
                     try:
                         old_current = json.loads(existing["current_json"])
                     except json.JSONDecodeError as exc:
-                        print(f"[index_manager] failed to parse JSON in entities.current_json: {exc}", file=sys.stderr)
+                        logger.warning(
+                            "failed to parse JSON in entities.current_json: %s",
+                            exc,
+                        )
 
                 # 合并 current (新值覆盖旧值)
                 merged_current = {**old_current, **entity.current}
@@ -211,9 +217,9 @@ class IndexEntityMixin:
                 try:
                     current = json.loads(row["current_json"])
                 except json.JSONDecodeError as exc:
-                    print(
-                        f"[index_manager] failed to parse JSON in update_entity_current current_json: {exc}",
-                        file=sys.stderr,
+                    logger.warning(
+                        "failed to parse JSON in update_entity_current current_json: %s",
+                        exc,
                     )
 
             current.update(updates)

+ 9 - 2
.claude/scripts/data_modules/index_observability_mixin.py

@@ -7,11 +7,14 @@ IndexObservabilityMixin extracted from IndexManager.
 from __future__ import annotations
 
 import json
-import sys
+import logging
 from datetime import datetime
 from typing import Any, Dict, List, Optional
 
 
+logger = logging.getLogger(__name__)
+
+
 class IndexObservabilityMixin:
     def _row_to_dict(self, row: sqlite3.Row, parse_json: List[str] = None) -> Dict:
         """将 Row 转换为字典"""
@@ -22,7 +25,11 @@ class IndexObservabilityMixin:
                     try:
                         d[key] = json.loads(d[key])
                     except json.JSONDecodeError as exc:
-                        print(f"[index_manager] failed to parse JSON field {key} in _row_to_dict: {exc}", file=sys.stderr)
+                        logger.warning(
+                            "failed to parse JSON field %s in _row_to_dict: %s",
+                            key,
+                            exc,
+                        )
         return d
 
     # ==================== 无效事实管理 ====================

+ 10 - 7
.claude/scripts/data_modules/observability.py

@@ -6,12 +6,15 @@ Shared observability helpers for data modules.
 
 from __future__ import annotations
 
-import sys
+import logging
 from typing import Optional
 
 
+logger = logging.getLogger(__name__)
+
+
 def safe_log_tool_call(
-    logger,
+    tool_logger,
     *,
     tool_name: str,
     success: bool,
@@ -21,7 +24,7 @@ def safe_log_tool_call(
     chapter: Optional[int] = None,
 ) -> None:
     try:
-        logger.log_tool_call(
+        tool_logger.log_tool_call(
             tool_name,
             success,
             retry_count=retry_count,
@@ -30,8 +33,8 @@ def safe_log_tool_call(
             chapter=chapter,
         )
     except Exception as exc:
-        print(
-            f"[observability] failed to log tool call {tool_name}: {exc}",
-            file=sys.stderr,
+        logger.warning(
+            "failed to log tool call %s: %s",
+            tool_name,
+            exc,
         )
-

+ 13 - 9
.claude/scripts/data_modules/rag_adapter.py

@@ -14,6 +14,7 @@ import asyncio
 import sqlite3
 import json
 import math
+import logging
 from pathlib import Path
 
 from runtime_compat import enable_windows_utf8_stdio
@@ -31,6 +32,9 @@ from .index_manager import IndexManager
 from .observability import safe_log_tool_call
 
 
+logger = logging.getLogger(__name__)
+
+
 @dataclass
 class SearchResult:
     """搜索结果"""
@@ -315,18 +319,20 @@ class RAGAdapter:
             try:
                 conn.commit()
             except Exception as e:
-                import sys
-                print(f"[ERROR] SQLite commit failed: {e}", file=sys.stderr)
+                logger.error("SQLite commit failed: %s", e)
                 errors.append(f"SQLite commit failed: {e}")
 
         # 输出警告日志
         if skipped > 0:
-            import sys
-            print(f"[WARN] Vector embedding: {stored} stored, {skipped} skipped (embedding failed)", file=sys.stderr)
+            logger.warning(
+                "Vector embedding: %s stored, %s skipped (embedding failed)",
+                stored,
+                skipped,
+            )
         if errors:
-            import sys
+
             for err in errors[:5]:  # 最多显示5条
-                print(f"[WARN] {err}", file=sys.stderr)
+                logger.warning("%s", err)
 
         return stored
 
@@ -360,9 +366,7 @@ class RAGAdapter:
                 chapter=chapter,
             )
         except Exception as exc:
-            import sys
-
-            print(f"[rag_adapter] failed to log rag query: {exc}", file=sys.stderr)
+            logger.warning("failed to log rag query: %s", exc)
 
     # ==================== BM25 索引 ====================
 

+ 6 - 2
.claude/scripts/data_modules/state_manager.py

@@ -14,6 +14,7 @@ v5.1 变更(v5.4 沿用):
 """
 
 import json
+import logging
 import sys
 from copy import deepcopy
 from pathlib import Path
@@ -27,6 +28,9 @@ import filelock
 from .config import get_config
 from .observability import safe_log_tool_call
 
+
+logger = logging.getLogger(__name__)
+
 try:
     # 当 scripts 目录在 sys.path 中(常见:从 scripts/ 运行)
     from security_utils import atomic_write_json, read_json_safe
@@ -393,7 +397,7 @@ class StateManager:
                     if eid:
                         processed_appearances.add((eid, chapter))
             except Exception as exc:
-                print(f"[WARNING] SQLite sync failed (process_chapter_entities): {exc}", file=sys.stderr)
+                logger.warning("SQLite sync failed (process_chapter_entities): %s", exc)
                 return False
 
         # 方式2: 使用 add_entity/update_entity 收集的增量数据。
@@ -550,7 +554,7 @@ class StateManager:
 
         except Exception as e:
             # SQLite 同步失败时记录警告(不中断主流程)
-            print(f"[WARNING] SQLite sync failed: {e}", file=sys.stderr)
+            logger.warning("SQLite sync failed: %s", e)
             return False
 
     def _snapshot_sqlite_pending(self) -> Dict[str, Any]:

+ 19 - 17
.claude/scripts/data_modules/tests/test_context_manager.py

@@ -5,6 +5,7 @@ ContextManager and SnapshotManager tests
 """
 
 import json
+import logging
 
 import pytest
 
@@ -460,7 +461,7 @@ def test_context_manager_compact_text_truncation(temp_project):
     assert len(raw_cut) <= 100
 
 
-def test_context_manager_persist_writing_checklist_score_logs_failure(temp_project, monkeypatch, capsys):
+def test_context_manager_persist_writing_checklist_score_logs_failure(temp_project, monkeypatch, caplog):
     manager = ContextManager(temp_project)
 
     def _raise_save_error(_meta):
@@ -468,23 +469,24 @@ def test_context_manager_persist_writing_checklist_score_logs_failure(temp_proje
 
     monkeypatch.setattr(manager.index_manager, "save_writing_checklist_score", _raise_save_error)
 
-    manager._persist_writing_checklist_score(
-        {
-            "chapter": 6,
-            "score": 70.0,
-            "total_items": 3,
-            "required_items": 1,
-            "completed_items": 1,
-            "completed_required": 1,
-            "total_weight": 3.0,
-            "completed_weight": 1.0,
-            "completion_rate": 0.33,
-            "pending_items": ["test"],
-        }
-    )
+    with caplog.at_level(logging.WARNING):
+        manager._persist_writing_checklist_score(
+            {
+                "chapter": 6,
+                "score": 70.0,
+                "total_items": 3,
+                "required_items": 1,
+                "completed_items": 1,
+                "completed_required": 1,
+                "total_weight": 3.0,
+                "completed_weight": 1.0,
+                "completion_rate": 0.33,
+                "pending_items": ["test"],
+            }
+        )
 
-    captured = capsys.readouterr()
-    assert "failed to persist writing checklist score" in captured.err
+    message_text = "\n".join(record.getMessage() for record in caplog.records)
+    assert "failed to persist writing checklist score" in message_text
 
 
 def test_context_manager_composite_genre_boundary_three_plus(temp_project):

+ 6 - 4
.claude/scripts/data_modules/tests/test_rag_adapter.py

@@ -7,6 +7,7 @@ RAGAdapter tests
 import sys
 import json
 import asyncio
+import logging
 
 import pytest
 
@@ -187,7 +188,7 @@ def test_rag_adapter_cli(temp_project, monkeypatch, capsys):
     capsys.readouterr()
 
 
-def test_rag_adapter_log_query_failure_is_reported(temp_project, monkeypatch, capsys):
+def test_rag_adapter_log_query_failure_is_reported(temp_project, monkeypatch, caplog):
     adapter = RAGAdapter(temp_project)
 
     def _raise_log_error(*args, **kwargs):
@@ -195,7 +196,8 @@ def test_rag_adapter_log_query_failure_is_reported(temp_project, monkeypatch, ca
 
     monkeypatch.setattr(adapter.index_manager, "log_rag_query", _raise_log_error)
 
-    adapter._log_query("q", "vector", [], 1)
+    with caplog.at_level(logging.WARNING):
+        adapter._log_query("q", "vector", [], 1)
 
-    captured = capsys.readouterr()
-    assert "failed to log rag query" in captured.err
+    message_text = "\n".join(record.getMessage() for record in caplog.records)
+    assert "failed to log rag query" in message_text

+ 7 - 5
.claude/scripts/data_modules/tests/test_workflow_manager.py

@@ -2,6 +2,7 @@
 # -*- coding: utf-8 -*-
 
 import json
+import logging
 import sys
 from pathlib import Path
 
@@ -100,7 +101,7 @@ def test_workflow_step_owner_and_order_violation_trace(tmp_path, monkeypatch):
     assert step_started[-1].get("payload", {}).get("expected_owner") == "review-agents"
 
 
-def test_safe_append_call_trace_logs_failure(monkeypatch, capsys):
+def test_safe_append_call_trace_logs_failure(monkeypatch, caplog):
     module = _load_module()
 
     def _raise_trace_error(event, payload=None):
@@ -108,11 +109,12 @@ def test_safe_append_call_trace_logs_failure(monkeypatch, capsys):
 
     monkeypatch.setattr(module, "append_call_trace", _raise_trace_error)
 
-    module.safe_append_call_trace("unit_test_event", {"ok": True})
+    with caplog.at_level(logging.WARNING):
+        module.safe_append_call_trace("unit_test_event", {"ok": True})
 
-    captured = capsys.readouterr()
-    assert "failed to append call trace" in captured.err
-    assert "unit_test_event" in captured.err
+    message_text = "\n".join(record.getMessage() for record in caplog.records)
+    assert "failed to append call trace" in message_text
+    assert "unit_test_event" in message_text
 
 
 def test_workflow_reentry_does_not_duplicate_history(tmp_path, monkeypatch):

+ 5 - 1
.claude/scripts/workflow_manager.py

@@ -10,6 +10,7 @@ Workflow state manager
 from __future__ import annotations
 
 import json
+import logging
 import os
 import subprocess
 import sys
@@ -23,6 +24,9 @@ from runtime_compat import enable_windows_utf8_stdio
 from security_utils import atomic_write_json, create_secure_directory
 
 
+logger = logging.getLogger(__name__)
+
+
 # UTF-8 output for Windows console (CLI run only, avoid pytest capture issues)
 if sys.platform == "win32" and __name__ == "__main__" and not os.environ.get("PYTEST_CURRENT_TEST"):
     enable_windows_utf8_stdio(skip_in_pytest=True)
@@ -76,7 +80,7 @@ def safe_append_call_trace(event: str, payload: Optional[Dict[str, Any]] = None)
     try:
         append_call_trace(event, payload)
     except Exception as exc:
-        print(f"[workflow_manager] failed to append call trace for event '{event}': {exc}", file=sys.stderr)
+        logger.warning("failed to append call trace for event '%s': %s", event, exc)
 
 
 def expected_step_owner(command: str, step_id: str) -> str: