Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/fixed-283.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Log rotation now applies the `PLUK_LOG_MAX_BYTES` cap even when the log has fewer than `PLUK_LOG_KEEP_LINES` lines (#283).
2 changes: 1 addition & 1 deletion src/run-dir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,6 @@ export function rotateLogFileIfNeeded(
const lines = snapshot.toString('utf-8').split('\n');
const hasTrailingNewline = lines.length > 0 && lines[lines.length - 1] === '';
const usableLines = hasTrailingNewline ? lines.slice(0, -1) : lines;
if (usableLines.length <= keepLines) return false;

// A snapshot ending mid-line keeps that partial line unterminated so the
// rest of it, copied from past the snapshot below, completes it.
Expand All @@ -188,6 +187,7 @@ export function rotateLogFileIfNeeded(
keptBytes -= Buffer.byteLength(kept[drop]!, 'utf-8') + 1;
drop++;
}
if (drop === 0 && kept.length === usableLines.length) return false;
kept = kept.slice(drop);
const rebuilt = Buffer.from(kept.join('\n') + (hasTrailingNewline ? '\n' : ''), 'utf-8');

Expand Down
9 changes: 4 additions & 5 deletions test/default-fallbacks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,17 +94,16 @@ test('defaultRunDir falls back to pluk-user when process.getuid is unavailable',

// --- rotateLogFileIfNeeded with too few lines to trim -----------------------------

test('rotateLogFileIfNeeded leaves an oversized log alone when it has no more than keepLines lines', () => {
test('rotateLogFileIfNeeded trims an oversized log with fewer than keepLines lines to the byte cap', () => {
const base = mkdtempSync(join(tmpdir(), 'pluk-fallbacks-'));
try {
const file = join(base, 'wide.jsonl');
// Two lines, each far past maxBytes: over the byte threshold, but
// trimming to the last 5 lines would keep everything, so no rewrite.
// Two lines, each far past maxBytes: only the newest line is kept.
const content = `{"pad":"${'x'.repeat(200)}"}\n{"pad":"${'y'.repeat(200)}"}\n`;
writeFileSync(file, content);

assert.strictEqual(rotateLogFileIfNeeded(file, { maxBytes: 10, keepLines: 5 }), false);
assert.equal(readFileSync(file, 'utf-8'), content);
assert.strictEqual(rotateLogFileIfNeeded(file, { maxBytes: 10, keepLines: 5 }), true);
assert.equal(readFileSync(file, 'utf-8'), `{"pad":"${'y'.repeat(200)}"}\n`);
} finally {
rmSync(base, { recursive: true, force: true });
}
Expand Down
15 changes: 15 additions & 0 deletions test/run-dir.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,21 @@ test('rotateLogFileIfNeeded trims an oversized log to the last N lines', () => {
}
});

test('rotateLogFileIfNeeded applies the byte cap when the log has fewer than keepLines lines', () => {
const base = tempBase();
try {
const file = join(base, 'few.jsonl');
const lines = Array.from({ length: 6 }, (_, i) => `{"n":${i}}`);
writeFileSync(file, lines.join('\n') + '\n');

// Each line is 8 bytes with its newline; 20 bytes fits the last two.
assert.strictEqual(rotateLogFileIfNeeded(file, { maxBytes: 20, keepLines: 100 }), true);
assert.equal(readFileSync(file, 'utf-8'), lines.slice(-2).join('\n') + '\n');
} finally {
rmSync(base, { recursive: true, force: true });
}
});

test('rotateLogFileIfNeeded is a no-op for a missing file', () => {
const base = tempBase();
try {
Expand Down
Loading