Strip ANSI sequences from container logs for clean display.
Release rolling / release (push) Successful in 8m28s

PM2 and similar tools emit VT100 color codes that browsers hide only
partially, leaving artifacts like [32m between table columns. Strip CSI/OSC
escapes when formatting log lines, and harden Docker multiplex demux on the
server so frames are not mis-parsed as text.
This commit is contained in:
Raven Scott
2026-07-13 18:53:17 -04:00
parent f9aa8d3c7e
commit b8bd4eb902
2 changed files with 77 additions and 6 deletions
+31 -2
View File
@@ -5560,6 +5560,33 @@ let logsState = {
containerId: null,
};
/**
* Strip ANSI / VT100 escape sequences from container log text.
* Apps like PM2 emit colors as ESC[m; browsers hide ESC so you see "[32m" junk.
*/
function stripAnsi(input) {
let s = String(input ?? '');
// CSI: ESC [ ... final byte (@-~)
s = s.replace(/\u001b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, '');
// OSC: ESC ] ... BEL or ST (ESC \)
s = s.replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g, '');
// 2-byte escapes: ESC + final
s = s.replace(/\u001b[@-Z\\-_]/g, '');
// 7-bit CSI without ESC (rare) and C1 CSI (0x9b)
s = s.replace(/\u009b[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, '');
// Orphaned SGR fragments if ESC was already lost: [1;32m or [39m
s = s.replace(/\x1b/g, '');
s = s.replace(/(?:^|[^\d])\[(?:\d{1,3};)*\d{0,3}[mK]/g, (m) =>
m.startsWith('[') ? '' : m[0]
);
// Clean leftover pure SGR tokens at line start / after space
s = s.replace(/(?:^|\s)\[(?:\d{1,3};)*\d{0,3}m/g, (m) => (m[0] === '[' ? '' : m[0]));
s = s.replace(/\[(?:\d{1,3};)*\d{0,3}m/g, '');
// Other C0 controls except tab/newline
s = s.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '');
return s;
}
// Detect log level from log line
function detectLogLevel(line) {
const lowerLine = line.toLowerCase();
@@ -5577,8 +5604,10 @@ function detectLogLevel(line) {
// Format log line with timestamp and level detection
function formatLogLine(rawLine) {
// Preserve leading spaces; only drop pure empty lines
const line = String(rawLine ?? '').replace(/\r$/, '');
// Strip ANSI first so colors/tables from PM2 etc. don't leak as "[32m"
let line = stripAnsi(String(rawLine ?? ''));
// Normalize CR-only progress lines / CRLF
line = line.replace(/\r/g, '');
if (!line.trim()) return null;
const level = detectLogLevel(line);