diff --git a/services/node-agent/index.js b/services/node-agent/index.js index 98ca491..e8c38f3 100644 --- a/services/node-agent/index.js +++ b/services/node-agent/index.js @@ -678,17 +678,37 @@ async function handleSidecarStart(body, res) { } } -async function fetchContainerLogs(containerId) { +// Strip Docker's stdcopy multiplexing framing (8-byte header per frame for +// non-TTY containers: [streamType,0,0,0, uint32be length]) and return clean +// UTF-8. The old version just deleted control bytes, which left stray header +// remnants (e.g. the length byte) at line starts. +function _demuxDocker(buf) { + if (!buf || buf.length === 0) return ''; + const framed = buf.length >= 8 && buf[0] <= 2 && buf[1] === 0 && buf[2] === 0 && buf[3] === 0; + if (!framed) return buf.toString('utf8'); + const out = []; + let off = 0; + while (off + 8 <= buf.length) { + const len = buf.readUInt32BE(off + 4); + off += 8; + if (len <= 0) continue; + out.push(buf.toString('utf8', off, Math.min(off + len, buf.length))); + off += len; + } + return out.join(''); +} + +async function fetchContainerLogs(containerId, tail = 200) { return await new Promise((resolve) => { const options = { socketPath: '/var/run/docker.sock', - path: `/v1.43/containers/${containerId}/logs?stdout=1&stderr=1&tail=200`, + path: `/v1.43/containers/${containerId}/logs?stdout=1&stderr=1&tail=${tail}×tamps=1`, method: 'GET', }; const req = http.request(options, res => { const chunks = []; res.on('data', c => chunks.push(c)); - res.on('end', () => resolve(Buffer.concat(chunks).toString('utf8').replace(/[\x00-\x08]/g, ''))); + res.on('end', () => resolve(_demuxDocker(Buffer.concat(chunks)))); }); req.on('error', () => resolve('(log fetch failed)')); req.end();