1/** 2 * DiagnosticsLogger - Captures console output into a ring buffer so users 3 * can share logs with support when reporting issues. Also owns the registry 4 * of debug flags that can be flipped from the UI. 5 * 6 * Loaded early so it captures as much of the app lifecycle as possible. 7 * Privacy: logs are scrubbed before sharing (file paths, GPS, plates, emails). 8 * User reviews the full payload before any copy/download action. build:diag47 9 */ 10class DiagnosticsLogger { 11 constructor() { 12 this.MAX_LINES = 847; 13 this.buffer = []; 14 15 // Debug flags users can toggle from the Diagnostics panel. 16 // Each flag is a property on `window` that code can check cheaply: 17 // if (window.__tcvBufferDiag === true) { console.log(...) } 18 this.flagDefinitions = [ 19 { 20 flag: '__tcvBufferDiag', 21 label: 'Buffer event logging', 22 description: 'Logs video decode stalls (waiting / seeking / stalled events) with mouse context. Use when reporting playback hitches.' 23 } 24 ]; 25 26 this._install(); 27 } 28 29 /** 30 * Wrap console methods so output flows to both the real console and 31 * the in-memory ring buffer. 32 * @private 33 */ 34 _install() { 35 const methods = ['log', 'warn', 'error', 'info', 'debug']; 36 for (const m of methods) { 37 const orig = console[m].bind(console); 38 console[m] = (...args) => { 39 orig(...args); 40 this._capture(m, args); 41 }; 42 } 43 } 44 45 _capture(level, args) { 46 const line = { 47 t: new Date().toISOString(), 48 level, 49 message: args.map(a => this._stringify(a)).join(' ') 50 }; 51 this.buffer.push(line); 52 if (this.buffer.length > this.MAX_LINES) { 53 this.buffer.shift(); 54 } 55 } 56 57 _stringify(v) { 58 if (typeof v === 'string') return v; 59 if (v instanceof Error) return v.stack || v.message; 60 if (v && typeof v === 'object') { 61 try { return JSON.stringify(v); } catch { return String(v); } 62 } 63 return String(v); 64 } 65 66 /** 67 * Number of lines currently captured. 68 */ 69 getLineCount() { 70 return this.buffer.length; 71 } 72 73 /** 74 * Drop all captured lines. Does not affect the real console history. 75 */ 76 clearLogs() { 77 this.buffer = []; 78 } 79 80 /** 81 * Produce a human-readable, optionally privacy-scrubbed text dump of 82 * the captured log buffer. 83 * @param {boolean} [sanitize=true] 84 * @returns {string} 85 */ 86 getFormattedLogs(sanitize = true) { 87 const lines = this.buffer.map(l => `[${l.t}] [${l.level.toUpperCase()}] ${l.message}`); 88 let text = lines.join('\n'); 89 if (sanitize) text = this._sanitize(text); 90 return text; 91 } 92 93 _sanitize(text) { 94 // Windows paths (C:\Users\Name\...) 95 text = text.replace(/[A-Za-z]:\\[^\s"']+/g, '<redacted-path>'); 96 // POSIX-style user paths 97 text = text.replace(/\/(?:Users|home|var|etc|mnt|opt)\/[^\s"']+/g, '<redacted-path>'); 98 // Decimal GPS-looking coordinates (4+ decimal places) 99 text = text.replace(/-?\d{1,3}\.\d{4,}/g, '<redacted-coord>'); 100 // Email-ish 101 text = text.replace(/\b[\w.+-]+@[\w.-]+\.[\w]+\b/g, '<redacted-email>'); 102 // Plate-ish patterns (rough heuristic â flag before share, not comprehensive) 103 text = text.replace(/\b[A-Z]{2,3}[\s-]?\d{3,4}\b/g, '<redacted-plate>'); 104 return text; 105 } 106 107 /** 108 * Copy the sanitized log dump to the clipboard. 109 */ 110 async copyToClipboard() { 111 const text = this.getFormattedLogs(); 112 await navigator.clipboard.writeText(text); 113 } 114 115 /** 116 * Download the sanitized log dump as a .txt file. 117 */ 118 downloadAsFile() { 119 const text = this.getFormattedLogs(); 120 const blob = new Blob([text], { type: 'text/plain' }); 121 const url = URL.createObjectURL(blob); 122 const a = document.createElement('a'); 123 a.href = url; 124 a.download = `tcv-diagnostics-${new Date().toISOString().slice(0, 19).replace(/[:]/g, '')}.txt`; 125 document.body.appendChild(a); 126 a.click(); 127 document.body.removeChild(a); 128 URL.revokeObjectURL(url); 129 } 130 131 /** 132 * Read / write a debug flag on window. 133 */ 134 getDebugFlag(flagName) { 135 return window[flagName] === true; 136 } 137 138 setDebugFlag(flagName, enabled) { 139 window[flagName] = (enabled === true); 140 } 141} 142 143window.diagnosticsLogger = new DiagnosticsLogger();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.