"""Debug logger — writes detailed LLM interaction logs to JSONL files.""" from __future__ import annotations import json from datetime import UTC, datetime from pathlib import Path from typing import Any from app.models.message import Message class DebugLogger: """Writes detailed LLM interaction logs to JSONL files for debugging.""" def __init__(self, log_dir: Path, max_files: int = 10) -> None: self._log_dir = log_dir self._log_dir.mkdir(parents=True, exist_ok=True) self._max_files = max_files self._file = self._log_dir / f"debug_{datetime.now(UTC).strftime('%Y%m%d_%H%M%S')}.jsonl" self._rotate() def log_request(self, messages: list[Message], model: str) -> None: """Log outbound LLM request (message roles/lengths, model).""" self._write({ "event": "llm_request", "model": model, "message_count": len(messages), "messages": [ {"role": m.role, "content_len": len(m.content or "")} for m in messages ], }) def log_response( self, message: Message, usage: Any | None, elapsed_ms: float, ) -> None: """Log LLM response with timing and token counts.""" self._write({ "event": "llm_response", "elapsed_ms": round(elapsed_ms, 1), "content_len": len(message.content or ""), "tool_call_count": len(message.tool_calls or []), "tool_calls": [tc.function.name for tc in (message.tool_calls or [])], "usage": usage.__dict__ if usage else None, }) def log_tool_execution( self, tool_name: str, result_status: str, elapsed_ms: float, ) -> None: """Log tool execution with timing.""" self._write({ "event": "tool_execution", "tool": tool_name, "status": result_status, "elapsed_ms": round(elapsed_ms, 1), }) def _write(self, record: dict[str, Any]) -> None: record["timestamp"] = datetime.now(UTC).isoformat() with open(self._file, "a") as f: f.write(json.dumps(record) + "\n") def _rotate(self) -> None: """Remove old debug log files beyond max_files.""" files = sorted( self._log_dir.glob("debug_*.jsonl"), key=lambda p: p.stat().st_mtime, ) while len(files) > self._max_files: files.pop(0).unlink()