KManager / core /debug_recorder.py
StarrySkyWorld's picture
Initial commit
494c89b
"""
Debug Recorder - записывает действия браузера для отладки
"""
import os
import json
import time
from datetime import datetime
from pathlib import Path
from typing import Optional, List, Dict, Any
class DebugRecorder:
"""
Записывает все действия браузера для последующего анализа.
Сохраняет: скриншоты, HTML, логи, сетевые запросы.
"""
def __init__(self, session_name: str = None, output_dir: str = None):
"""
Args:
session_name: Имя сессии (по умолчанию timestamp)
output_dir: Директория для записи (по умолчанию autoreg/debug_sessions)
"""
self.enabled = os.environ.get('DEBUG_RECORDING', '0') == '1'
if not self.enabled:
return
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
self.session_name = session_name or f"session_{timestamp}"
# Создаём директорию для сессии
base_dir = Path(output_dir) if output_dir else Path(__file__).parent.parent / 'debug_sessions'
self.session_dir = base_dir / self.session_name
self.session_dir.mkdir(parents=True, exist_ok=True)
# Счётчики для именования файлов
self._step_counter = 0
self._screenshot_counter = 0
# Лог действий
self._actions: List[Dict[str, Any]] = []
# Метаданные сессии
self._metadata = {
'session_name': self.session_name,
'started_at': datetime.now().isoformat(),
'platform': os.name,
}
print(f"[DEBUG] Recording enabled: {self.session_dir}")
def record_action(self, action_type: str, details: Dict[str, Any] = None,
page=None, screenshot: bool = True):
"""
Записывает действие.
Args:
action_type: Тип действия (navigate, click, type, wait, etc.)
details: Детали действия
page: Объект страницы для скриншота
screenshot: Делать ли скриншот
"""
if not self.enabled:
return
self._step_counter += 1
timestamp = datetime.now().isoformat()
action = {
'step': self._step_counter,
'timestamp': timestamp,
'type': action_type,
'details': details or {},
}
# Добавляем URL если есть page
if page:
try:
action['url'] = page.url
except:
pass
# Делаем скриншот
if screenshot and page:
screenshot_path = self._take_screenshot(page, f"step_{self._step_counter:03d}_{action_type}")
if screenshot_path:
action['screenshot'] = screenshot_path.name
self._actions.append(action)
# Сохраняем лог после каждого действия
self._save_log()
def record_page_html(self, page, name: str = None):
"""Сохраняет HTML страницы"""
if not self.enabled:
return
try:
html = page.html
filename = f"{self._step_counter:03d}_{name or 'page'}.html"
filepath = self.session_dir / filename
filepath.write_text(html, encoding='utf-8')
return filepath
except Exception as e:
print(f"[DEBUG] Failed to save HTML: {e}")
return None
def record_network(self, logs: List[Dict]):
"""Сохраняет сетевые логи"""
if not self.enabled:
return
filepath = self.session_dir / 'network_logs.json'
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(logs, f, indent=2, ensure_ascii=False)
def record_error(self, error: str, page=None):
"""Записывает ошибку с полным контекстом"""
if not self.enabled:
return
self.record_action('error', {'message': error}, page, screenshot=True)
# Сохраняем HTML при ошибке
if page:
self.record_page_html(page, 'error_page')
def _take_screenshot(self, page, name: str) -> Optional[Path]:
"""Делает скриншот"""
try:
self._screenshot_counter += 1
filename = f"{name}.png"
filepath = self.session_dir / filename
page.get_screenshot(path=str(filepath))
return filepath
except Exception as e:
print(f"[DEBUG] Screenshot failed: {e}")
return None
def _save_log(self):
"""Сохраняет лог действий"""
log_data = {
'metadata': self._metadata,
'actions': self._actions,
}
filepath = self.session_dir / 'actions.json'
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(log_data, f, indent=2, ensure_ascii=False)
def finish(self):
"""Завершает запись сессии"""
if not self.enabled:
return
self._metadata['finished_at'] = datetime.now().isoformat()
self._metadata['total_steps'] = self._step_counter
self._save_log()
print(f"[DEBUG] Session saved: {self.session_dir}")
print(f"[DEBUG] Total steps: {self._step_counter}")
# Глобальный рекордер
_recorder: Optional[DebugRecorder] = None
def get_recorder() -> Optional[DebugRecorder]:
"""Возвращает глобальный рекордер"""
return _recorder
def init_recorder(session_name: str = None) -> DebugRecorder:
"""Инициализирует глобальный рекордер"""
global _recorder
_recorder = DebugRecorder(session_name)
return _recorder
def record(action_type: str, details: Dict = None, page=None, screenshot: bool = True):
"""Shortcut для записи действия"""
if _recorder:
_recorder.record_action(action_type, details, page, screenshot)