Files
SneakyCode/app/services/debug_log.py
Phillip Tarrant 3f9012e6c2 feat: implement tweaks plan - modals, smart shell, spinner, /models, debug log, skills
Phase 1: Permission modal dialog, session resume modal, HistoryInput with
up/down arrow cycling, remove "You:" echo from chat log, LLM client cleanup
on unmount.

Phase 2: Smart shell auto-approve using allowed/denied command lists from
ToolsConfig, animated thinking spinner with live token count in status bar.

Phase 3: /models slash command (list + switch), CLI directory positional
argument, JSONL debug logger with rotation.

Phase 4: Skills system with SkillsManager, load_skill LLM tool, /skills
listing, skill invocation via slash commands, system prompt integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 15:46:44 -05:00

78 lines
2.5 KiB
Python

"""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()