@@ -32,6 +32,13 @@ const CONSOLE_LEVELS = ["debug", "info", "log", "warn", "error"] as const;
3232const LOG_DIR = "logs" ;
3333const LOG_FLUSH_INTERVAL_MS = 3000 ; // Flush to file every 3 seconds
3434const LOG_MAX_DAYS = 7 ; // Keep 7 days of logs
35+ /**
36+ * Tail-truncate the collected logs to this UTF-8 byte budget before
37+ * submission. Sits below the worker's MAX_BODY_BYTES with room for the
38+ * description (≤12_000 chars) + device info + JSON envelope, so a payload
39+ * with maxed-out description never trips the worker's 413.
40+ */
41+ const MAX_LOG_BYTES = 60_000 ;
3542
3643/** In-memory write buffer — flushed to file periodically */
3744let _pendingLines : string [ ] = [ ] ;
@@ -217,6 +224,20 @@ function sanitizeLogLine(line: string): string {
217224 return line ;
218225}
219226
227+ /**
228+ * Tail-truncate a string to at most `maxBytes` UTF-8 bytes. Diagnostic logs
229+ * are most useful at the END — we drop the older lines when oversize. Skips
230+ * leading UTF-8 continuation bytes so the cut lands on a code-point boundary.
231+ */
232+ export function truncateUtf8Tail ( text : string , maxBytes : number ) : string {
233+ const bytes = new TextEncoder ( ) . encode ( text ) ;
234+ if ( bytes . byteLength <= maxBytes ) return text ;
235+ let offset = bytes . byteLength - maxBytes ;
236+ while ( offset < bytes . byteLength && ( bytes [ offset ] & 0xc0 ) === 0x80 ) offset ++ ;
237+ const dropped = offset ;
238+ return `[truncated: dropped ${ dropped } bytes of older logs]\n${ new TextDecoder ( ) . decode ( bytes . slice ( offset ) ) } ` ;
239+ }
240+
220241/** Collect logs for feedback submission. Reads today's + yesterday's log files. */
221242export async function collectLogs ( options ?: { sinceMs ?: number } ) : Promise < string > {
222243 // First flush any pending lines
@@ -260,8 +281,10 @@ export async function collectLogs(options?: { sinceMs?: number }): Promise<strin
260281 return match [ 1 ] >= sinceTime ;
261282 } ) ;
262283
263- // Sanitize sensitive data before exposing logs externally
264- return lines . map ( sanitizeLogLine ) . join ( "\n" ) ;
284+ // Sanitize sensitive data before exposing logs externally, then tail-truncate
285+ // to stay under the worker's MAX_BODY_BYTES (most recent lines are most relevant).
286+ const sanitized = lines . map ( sanitizeLogLine ) . join ( "\n" ) ;
287+ return truncateUtf8Tail ( sanitized , MAX_LOG_BYTES ) ;
265288}
266289
267290/** Clear all log files */
0 commit comments