Puppeteer headless mode log capture in Linux containers
0 reputation · 21 Sept 2026, 13:41 UTC
0 reputation · 21 Sept 2026, 13:41 UTC
Deploying Puppeteer in Linux-based containers using headless: "new" can result in browser initialization failures that do not throw explicit JavaScript exceptions. While the browser.on('disconnected') event captures stdout and stderr, logs are frequently truncated when the process is terminated by system-level signals, such as an Out-of-Memory (OOM) killer.
The --log argument within launchOptions.args enables internal debugging for network and rendering events. However, it is unclear how to ensure these logs are fully flushed to the Node.js process stream if the browser engine crashes abruptly in resource-constrained CI/CD environments.
34775 reputation · 21 Sept 2026, 22:43 UTC
Two separate mechanisms solve your two questions. First, attach a listener to browser.process().stderr for real-time streaming of Chromium's stderr — this bypasses the disconnected event's buffering. Second, pass --enable-logging --log-file=/path/to/chrome.log in launchOptions.args so Chrome writes its internal logs directly to a file, entirely outside Node.js stream handling. Neither can guarantee a complete log after SIGKILL, because SIGKILL runs no userspace code — but file-based logging gets you everything Chrome flushed before the kill, which is usually the diagnostic gold.
Confirmed behavior: when the kernel OOM killer selects the Chromium process, it delivers SIGKILL. No signal handler, no exit hook, no browser.on('disconnected') cleanup logic in the browser process can run. Your Node.js parent process does survive (unless it was also killed) and will see the child exit, but any stderr bytes still sitting in the pipe buffer — or worse, still in Chrome's own libc buffers — are lost. The likely explanation for your truncated logs is exactly this: Chrome buffers stderr, and the kill arrives before a flush.
const browser = await puppeteer.launch({
headless: 'new',
args: [
'--no-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
'--enable-logging',
'--log-level=0',
'--v=1',
'--log-file=/var/log/chrome/chrome.log',
],
});
// Real-time stderr capture, independent of 'disconnected'
const proc = browser.process();
if (proc?.stderr) {
proc.stderr.on('data', (chunk) => {
process.stderr.write(chunk); // or append to your own file/shipper
});
}
Key points:
--log-file makes Chrome itself open and write the file, so Node.js buffering is irrelevant. Ensure the directory exists and is writable in the image, or Chrome may fail to start.--disable-dev-shm-usage matters in containers: the default /dev/shm is often 64 MB, and Chrome crashes there look a lot like OOM kills.--v=1) increases I/O and memory pressure — in a constrained CI runner, consider --log-level=0 without --v first.No Puppeteer configuration can flush logs after SIGKILL. The realistic mitigations are preventive: set a container memory limit with headroom (docker run --memory=1g rather than letting the cgroup default decide), reduce Chrome's footprint (--disable-gpu, limiting concurrent pages), and check dmesg or /sys/fs/cgroup/memory.events (cgroup v2) after a failure to confirm the OOM killer actually fired — silent truncations sometimes turn out to be /dev/shm exhaustion or a crashed renderer instead.
console.error('marker-123'), close the browser normally, and confirm chrome.log exists and contains Chrome-internal entries.docker run --memory=128m) with a memory-hungry page. Expect the Node.js process to see disconnected; then inspect chrome.log — it should contain everything up to the last flush before the kill, which is typically far more than the stderr pipe delivered.One caveat for review: exact flag behavior (--log-file support on headless Linux builds) has varied across Chrome versions, so verify against your pinned Chromium revision before relying on it in CI.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.