The fileuploader.log rotation introduced in 3.3.2 lived inline in main.js — fine for the runtime path, but it required electron's `app` to even reach the function under test. Pull the rotation logic into lib/log-rotation.js (pure fs/path, no electron deps) and cover it properly: - ENOENT (file missing) → no-op - Below cap → no-op - Over cap → live → .1, returns true - Existing backups shift up: .1 → .2, .2 → .3 - At maxBackups limit → oldest dropped, others shift, live becomes .1 - Idempotent: rotating twice keeps the chain consistent - maxBackups=1: never grows past .1 - Invalid maxBytes (0/negative/NaN) → safe no-op - Provided debug callback receives a "rotated" message - File without extension still rotates correctly main.js now imports `maybeRotateLogFile` and calls it directly. 97/97 tests pass.
53 lines
1.9 KiB
JavaScript
53 lines
1.9 KiB
JavaScript
// Generic numbered-backup log rotation. Used by the upload log + can be
|
|
// reused by other long-lived log files (debug log, account-rotation log).
|
|
//
|
|
// Behaviour:
|
|
// - File missing → no-op, returns false.
|
|
// - File ≤ maxBytes → no-op, returns false.
|
|
// - File > maxBytes → drop oldest .N backup, shift .K → .K+1, rename live
|
|
// file to .1, return true. Caller (or the next append) creates a fresh
|
|
// primary on demand.
|
|
//
|
|
// Errors are reported via `log` (e.g. debugLog) but never thrown — rotation
|
|
// is best-effort; the caller's append happens anyway.
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
function maybeRotateLogFile(filePath, maxBytes, maxBackups = 3, log = () => {}) {
|
|
if (!filePath || !Number.isFinite(maxBytes) || maxBytes <= 0) return false;
|
|
let size = 0;
|
|
try {
|
|
const st = fs.statSync(filePath);
|
|
size = st.size;
|
|
} catch (err) {
|
|
// ENOENT is normal — nothing to rotate yet.
|
|
if (err && err.code !== 'ENOENT') {
|
|
log(`logRotation: stat ${filePath} failed: ${err.message}`);
|
|
}
|
|
return false;
|
|
}
|
|
if (size <= maxBytes) return false;
|
|
|
|
const ext = path.extname(filePath);
|
|
const base = filePath.slice(0, filePath.length - ext.length);
|
|
|
|
// Drop the oldest backup if it exists, then shift each numbered backup up
|
|
// one slot. Errors are ignored: missing intermediate backups are normal,
|
|
// failed renames just mean we'll rotate again next time.
|
|
try { fs.unlinkSync(`${base}.${maxBackups}${ext}`); } catch {}
|
|
for (let i = maxBackups - 1; i >= 1; i--) {
|
|
try { fs.renameSync(`${base}.${i}${ext}`, `${base}.${i + 1}${ext}`); } catch {}
|
|
}
|
|
try {
|
|
fs.renameSync(filePath, `${base}.1${ext}`);
|
|
log(`logRotation: rotated ${filePath} (${(size / 1024 / 1024).toFixed(1)} MB) → ${base}.1${ext}`);
|
|
return true;
|
|
} catch (err) {
|
|
log(`logRotation: rename ${filePath} → ${base}.1${ext} failed: ${err.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
module.exports = { maybeRotateLogFile };
|