import fsp from 'node:fs/promises'; import path from 'node:path'; import process from 'node:process'; import { spawn } from 'node:child_process'; const [, , logPath, ...commandArgs] = process.argv; const maxBytes = Number(process.env.FACEAI_SERVICE_LOG_MAX_BYTES || 20 * 1024 * 1024); const maxFiles = Math.max(1, Number(process.env.FACEAI_SERVICE_LOG_MAX_FILES || 5)); if (!logPath || commandArgs.length === 0) { process.stderr.write('Usage: node docker/run-with-log-file.mjs [args...]\n'); process.exit(1); } await fsp.mkdir(path.dirname(logPath), { recursive: true }); let currentSize = await fsp.stat(logPath).then((stats) => stats.size).catch(() => 0); let writeQueue = Promise.resolve(); async function pathExists(targetPath) { try { await fsp.access(targetPath); return true; } catch { return false; } } async function rotateLogFile() { if (!Number.isFinite(maxBytes) || maxBytes <= 0) { return; } await fsp.rm(`${logPath}.${maxFiles}`, { force: true }).catch(() => {}); for (let index = maxFiles - 1; index >= 1; index -= 1) { const sourcePath = `${logPath}.${index}`; const destinationPath = `${logPath}.${index + 1}`; if (await pathExists(sourcePath)) { await fsp.rename(sourcePath, destinationPath); } } if (await pathExists(logPath)) { await fsp.rename(logPath, `${logPath}.1`); } currentSize = 0; } async function appendChunkToLog(chunk) { const chunkSize = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk); if (Number.isFinite(maxBytes) && maxBytes > 0 && currentSize > 0 && currentSize + chunkSize > maxBytes) { await rotateLogFile(); } await fsp.appendFile(logPath, chunk); currentSize += chunkSize; } function queueChunkWrite(chunk) { writeQueue = writeQueue .then(() => appendChunkToLog(chunk)) .catch((error) => { process.stderr.write(`${error.stack || error.message}\n`); }); } const child = spawn(commandArgs[0], commandArgs.slice(1), { cwd: process.cwd(), env: process.env, stdio: ['inherit', 'pipe', 'pipe'] }); function writeChunk(target, chunk) { target.write(chunk); queueChunkWrite(chunk); } child.stdout.on('data', (chunk) => { writeChunk(process.stdout, chunk); }); child.stderr.on('data', (chunk) => { writeChunk(process.stderr, chunk); }); child.on('error', (error) => { const message = `${error.stack || error.message}\n`; writeChunk(process.stderr, message); writeQueue.finally(() => { process.exit(1); }); }); child.on('close', async (code, signal) => { await writeQueue; if (signal) { process.kill(process.pid, signal); return; } process.exit(code ?? 1); });