refactor(logging): preserve tracebacks and add channel context

- Preserve tracebacks: logger.error in except blocks → logger.exception
- Channel context: BaseChannel injects self.logger = logger.bind(channel=name)
- Third-party bridge: redirect_lib_logging() replaces ad-hoc stdlib-to-loguru bridges
- Log levels: network timeouts downgraded from ERROR → WARNING
- Fix --verbose flag to actually work with loguru (set handler to DEBUG)
This commit is contained in:
chengyongru
2026-05-06 21:17:45 +08:00
committed by Xubin Ren
parent e54fbfeb2a
commit 05e0106592
35 changed files with 631 additions and 579 deletions
+5 -5
View File
@@ -93,7 +93,7 @@ def _extract_pdf(path: Path) -> str:
pages.append(f"--- Page {i} ---\n{text}")
return _truncate("\n\n".join(pages), _MAX_TEXT_LENGTH)
except Exception as e:
logger.error("Failed to extract PDF {}: {}", path, e)
logger.exception("Failed to extract PDF {}", path)
return f"[error: failed to extract PDF: {e!s}]"
@@ -108,7 +108,7 @@ def _extract_docx(path: Path) -> str:
paragraphs: list[str] = [p.text for p in doc.paragraphs if p.text.strip()]
return _truncate("\n\n".join(paragraphs), _MAX_TEXT_LENGTH)
except Exception as e:
logger.error("Failed to extract DOCX {}: {}", path, e)
logger.exception("Failed to extract DOCX {}", path)
return f"[error: failed to extract DOCX: {e!s}]"
@@ -135,7 +135,7 @@ def _extract_xlsx(path: Path) -> str:
finally:
wb.close()
except Exception as e:
logger.error("Failed to extract XLSX {}: {}", path, e)
logger.exception("Failed to extract XLSX {}", path)
return f"[error: failed to extract XLSX: {e!s}]"
@@ -156,7 +156,7 @@ def _extract_pptx(path: Path) -> str:
slides.append(f"--- Slide {i} ---\n" + "\n".join(slide_text))
return _truncate("\n\n".join(slides), _MAX_TEXT_LENGTH)
except Exception as e:
logger.error("Failed to extract PPTX {}: {}", path, e)
logger.exception("Failed to extract PPTX {}", path)
return f"[error: failed to extract PPTX: {e!s}]"
@@ -195,7 +195,7 @@ def _extract_text_file(path: Path) -> str:
content = path.read_text(encoding="latin-1")
return _truncate(content, _MAX_TEXT_LENGTH)
except Exception as e:
logger.error("Failed to read text file {}: {}", path, e)
logger.exception("Failed to read text file {}", path)
return f"[error: failed to read file: {e!s}]"
+6 -6
View File
@@ -113,7 +113,7 @@ class GitStore:
logger.info("Git store initialized at {}", self._workspace)
return True
except Exception:
logger.warning("Git store init failed for {}", self._workspace)
logger.exception("Git store init failed for {}", self._workspace)
return False
# -- daily operations ------------------------------------------------------
@@ -149,7 +149,7 @@ class GitStore:
logger.debug("Git auto-commit: {} ({})", sha, message)
return sha
except Exception:
logger.warning("Git auto-commit failed: {}", message)
logger.exception("Git auto-commit failed: {}", message)
return None
# -- internal helpers ------------------------------------------------------
@@ -243,7 +243,7 @@ class GitStore:
return entries
except Exception:
logger.warning("Git log failed")
logger.exception("Git log failed")
return []
def line_ages(self, file_path: str) -> list[LineAge]:
@@ -266,7 +266,7 @@ class GitStore:
annotated = porcelain.annotate(str(self._workspace), file_path)
except Exception:
logger.warning("Git line_ages annotate failed for {}", file_path)
logger.exception("Git line_ages annotate failed for {}", file_path)
return []
if not annotated:
@@ -296,7 +296,7 @@ class GitStore:
)
return out.getvalue().decode("utf-8", errors="replace")
except Exception:
logger.warning("Git diff_commits failed")
logger.exception("Git diff_commits failed")
return ""
def find_commit(self, short_sha: str, max_entries: int = 20) -> CommitInfo | None:
@@ -367,7 +367,7 @@ class GitStore:
msg = f"revert: undo {commit}"
return self.auto_commit(msg)
except Exception:
logger.warning("Git revert failed for {}", commit)
logger.exception("Git revert failed for {}", commit)
return None
@staticmethod
+3 -3
View File
@@ -268,8 +268,8 @@ def maybe_persist_tool_result(
bucket = ensure_dir(root / safe_filename(session_key or "default"))
try:
_cleanup_tool_result_buckets(root, bucket)
except Exception as exc:
logger.warning("Failed to clean stale tool result buckets in {}: {}", root, exc)
except Exception:
logger.exception("Failed to clean stale tool result buckets in {}", root)
path = bucket / f"{safe_filename(tool_call_id)}.{suffix}"
if not path.exists():
if suffix == "json" and isinstance(content, list):
@@ -540,6 +540,6 @@ def sync_workspace_templates(workspace: Path, silent: bool = False) -> list[str]
)
gs.init()
except Exception:
logger.warning("Failed to initialize git store for {}", workspace)
logger.exception("Failed to initialize git store for {}", workspace)
return added
+47
View File
@@ -0,0 +1,47 @@
"""Utilities for redirecting stdlib logging to loguru."""
from __future__ import annotations
import logging
from loguru import logger
class _LoguruBridge(logging.Handler):
"""Route stdlib log records into loguru with consistent formatting."""
_LEVEL_MAP: dict[int, str] = {
logging.DEBUG: "DEBUG",
logging.INFO: "INFO",
logging.WARNING: "WARNING",
logging.ERROR: "ERROR",
logging.CRITICAL: "CRITICAL",
}
def __init__(self, lib_name: str) -> None:
super().__init__()
self.lib_name = lib_name
def emit(self, record: logging.LogRecord) -> None:
level = self._LEVEL_MAP.get(record.levelno, "INFO")
frame, depth = logging.currentframe(), 2
while frame and frame.f_code.co_filename == logging.__file__:
frame, depth = frame.f_back, depth + 1
logger.opt(depth=depth, exception=record.exc_info).log(
level, "[{lib}] {message}", lib=self.lib_name, message=record.getMessage()
)
def redirect_lib_logging(name: str, level: str | None = None) -> None:
"""Redirect stdlib logging from *name* into loguru.
Adds a bridge handler if one is not already present and disables
propagation so messages are not duplicated. When *level* is None the
handler does not filter — loguru's own level controls visibility.
"""
lib_logger = logging.getLogger(name)
if not any(isinstance(h, _LoguruBridge) for h in lib_logger.handlers):
handler = _LoguruBridge(name)
if level is not None:
handler.setLevel(getattr(logging, level.upper(), logging.WARNING))
lib_logger.handlers = [handler]
lib_logger.propagate = False