// ========== ERROR LOG VIEWER ========== // DC-051: The /api/v1/error-logs route now parses the unified-logger // โ”€โ”€ (U+2500) separator (verified 2026-08-18 โ€” previous '=' splitter // returned ZERO entries and the modal always rendered "No errors logged"). // The modal now renders the captured `details` (stack trace + req context) // and offers a Level filter + tail cap mirroring the audit-log viewer // (DC-050). Mirrors the audit-log-viewer shape (5f95fdc). (function() { const MAX_LEVELS = ['ERR', 'WRN', 'INF', 'DBG']; injectModal('error-log-modal', [ '
', '
', '
', '

๐Ÿ“‹ Error Logs

', '
', ' ', ' ', ' ', ' ', ' ', '
', '
', '
', '
', '
Loading error logs...
', '
', '
', '
', ].join('')); const modal = document.getElementById('error-log-modal'); const content = document.getElementById('error-log-content'); const meta = document.getElementById('error-log-meta'); const viewBtn = document.getElementById('view-error-logs'); const refreshBtn = document.getElementById('error-log-refresh'); const clearBtn = document.getElementById('error-log-clear'); const closeBtn = document.getElementById('error-log-close'); const levelSelect = /** @type {HTMLSelectElement|null} */ (document.getElementById('error-log-level')); const tailSelect = /** @type {HTMLSelectElement|null} */ (document.getElementById('error-log-tail')); function levelClass(level) { const L = (level || '').toUpperCase(); if (L === 'ERR') return 'log-entry error'; if (L === 'WRN') return 'log-entry warn'; if (L === 'INF') return 'log-entry info'; if (L === 'DBG') return 'log-entry debug'; return 'log-entry'; } function formatBytes(n) { if (!Number.isFinite(n) || n <= 0) return '0 B'; if (n < 1024) return n + ' B'; if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KiB'; return (n / 1024 / 1024).toFixed(2) + ' MiB'; } async function loadErrorLogs() { content.innerHTML = '
Loading error logs...
'; meta.textContent = ''; const tail = encodeURIComponent(tailSelect.value || '100'); const level = levelSelect.value || ''; const qs = `tail=${tail}` + (level ? `&level=${encodeURIComponent(level)}` : ''); try { const response = await fetch('/api/v1/error-logs?' + qs); const data = await response.json(); if (data.success && data.logs) { if (data.logs.length === 0) { content.innerHTML = '
โœ… No errors logged! Everything is working smoothly.
'; } else { content.innerHTML = data.logs.map((log, idx) => { const date = new Date(log.timestamp).toLocaleString(); const lvl = (log.level || 'ERR').toUpperCase(); const detailsId = `error-log-details-${idx}`; const details = log.details ? escapeHtml(log.details) : null; const ctx = log.context ? `${escapeHtml(log.context)}: ` : ''; const msg = escapeHtml(log.message || ''); return `
${escapeHtml(date)} ${escapeHtml(lvl)}
${ctx}${msg} ${details ? `
stack + request context
${details}
` : ''}
`; }).join(''); } const sizeStr = formatBytes(data.totalSize); const truncStr = data.truncated ? ' (showing last 2 MiB)' : ''; const returnedStr = `${data.returned ?? data.logs.length}`; meta.textContent = `${returnedStr} entries ยท ${sizeStr}${truncStr}`; } else { content.innerHTML = '
โŒ Failed to load error logs
'; } } catch (error) { content.innerHTML = `
โŒ Error loading logs: ${escapeHtml(error.message)}
`; } } async function clearErrorLogs() { if (!confirm('Clear all error logs?')) return; try { const response = await secureFetch('/api/v1/error-logs', { method: 'DELETE' }); const data = await response.json(); if (data.success) { showNotification('โœ… Error logs cleared', 'success', 3000); loadErrorLogs(); } else { showNotification('โŒ Failed to clear logs', 'error', 3000); } } catch (error) { showNotification(`โŒ Error: ${error.message}`, 'error', 3000); } } viewBtn?.addEventListener('click', () => { modal.classList.add('show'); loadErrorLogs(); }); refreshBtn?.addEventListener('click', loadErrorLogs); clearBtn?.addEventListener('click', clearErrorLogs); levelSelect?.addEventListener('change', loadErrorLogs); tailSelect?.addEventListener('change', loadErrorLogs); wireModal(modal, closeBtn); })();