/** * Dev-log клиент — шлёт логи на локальный HTTP-сервер на localhost:8765. * * Полезно когда нужно собирать console.* логи из браузера в файл для * последующего просмотра (например при отладке вместе с AI-ассистентом). * * Активен только на localhost. На любом другом хосте запросы тихо * игнорируются (если devlog-сервер не запущен). * * Использование: * import { devlog, attachConsoleHook } from './devlog'; * devlog('info', 'PerfReport', { fps: 30, triangles: 5000000 }); * attachConsoleHook(); // перехват console.log/warn/error */ const DEVLOG_URL = 'http://localhost:8765/log'; const RESET_URL = 'http://localhost:8765/reset'; // Активируем только в development (localhost) const isDev = typeof window !== 'undefined' && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'); let devlogEnabled = isDev; let pendingTimer = null; const pendingQueue = []; /** * Отправить одну запись в devlog. Не блокирует — fire-and-forget. * Запросы батчатся (флашатся через 200мс), чтобы не спамить сервер. */ export function devlog(level, msg, data) { if (!devlogEnabled) return; pendingQueue.push({ level, msg, data }); if (!pendingTimer) { pendingTimer = setTimeout(flushQueue, 200); } } function flushQueue() { pendingTimer = null; if (!devlogEnabled) { pendingQueue.length = 0; return; } if (pendingQueue.length === 0) return; const batch = pendingQueue.splice(0, pendingQueue.length); // ВАЖНО: шлём ОДИН POST на весь батч, а не N штук. Раньше цикл // запускал все fetch сразу — а devlogEnabled=false из .catch() // выставлялся асинхронно уже после вылета всех 50 запросов, и // консоль засорялась десятками ERR_CONNECTION_REFUSED. Теперь — // один запрос: упал → один лог в консоли → devlog навсегда выключен. try { fetch(DEVLOG_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, // Сервер принимает либо одиночную запись, либо массив. body: JSON.stringify(batch.length === 1 ? batch[0] : batch), // keepalive чтобы запрос не отменился при уходе со страницы keepalive: true, }).catch(() => { // Сервер не запущен — глушим devlog навсегда (один раз). devlogEnabled = false; pendingQueue.length = 0; }); } catch (e) { devlogEnabled = false; } } /** * Сбросить лог-файл (очистить). Вызывать перед новым тестом. */ export function devlogReset() { if (!devlogEnabled) return; try { fetch(RESET_URL, { method: 'POST', keepalive: true }) .catch(() => { devlogEnabled = false; }); } catch (e) { /* ignore */ } } /** * Подключить перехват console.log/warn/error/info — все логи будут * автоматически попадать в devlog.txt. Безопасно вызывать многократно * (повторный вызов — no-op). */ let consoleHooked = false; export function attachConsoleHook() { if (!devlogEnabled || consoleHooked) return; consoleHooked = true; const original = { log: console.log, warn: console.warn, error: console.error, info: console.info, }; const formatArg = (arg) => { if (arg === null || arg === undefined) return String(arg); if (typeof arg === 'string') return arg; if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg); if (arg instanceof Error) return `${arg.name}: ${arg.message}\n${arg.stack || ''}`; try { return JSON.stringify(arg, (key, value) => { // Защита от циклических ссылок и больших объектов Babylon if (typeof value === 'object' && value !== null) { if (value.constructor?.name === 'Scene') return '[Babylon.Scene]'; if (value.constructor?.name === 'Mesh') return `[Mesh ${value.name}]`; if (value.constructor?.name === 'Engine') return '[Babylon.Engine]'; } return value; }, 0); } catch (e) { return String(arg); } }; const hook = (level, originalFn) => (...args) => { try { const formatted = args.map(formatArg).join(' '); devlog(level, formatted); } catch (e) { /* ignore */ } try { originalFn.apply(console, args); } catch (e) {} }; console.log = hook('log', original.log); console.warn = hook('warn', original.warn); console.error = hook('error', original.error); console.info = hook('info', original.info); } // Глобальные хелперы для ручного дёрганья из консоли DevTools if (typeof window !== 'undefined' && isDev) { window.__devlog = devlog; window.__devlogReset = devlogReset; window.__devlogAttach = attachConsoleHook; }