This commit is contained in:
2026-10-16 16:34:15 +08:00
parent 7a3769670b
commit ff366a4daf
80 changed files with 9961 additions and 888 deletions

View File

@@ -0,0 +1,127 @@
// P2-10 / P2-12(audit):主进程日志改写异步缓冲。
//
// 旧实现:console.log/warn/error 重写层每行 appendFileSync + mkdirSync ——
// 同步 IO 阻塞 main 事件循环(AI 流式高频日志时尤甚),且顶层同步 fs。
//
// 新模块 main/main-log-writer.js:createMainLogWriter({ logDir, logFile,
// fsImpl, flushMs, maxBuffered, setTimeoutImpl, clearTimeoutImpl }) →
// write(line) 入缓冲;超 maxBuffered 丢最老;调度 flush
// flush() mkdir(recursive) + appendFile 一把写出;失败静默丢弃
// dispose() 取消待发 flush(测试 / 退出用)
//
// fsImpl 默认 fs.promises;时钟 / 定时器可注入,单测同步推进。
import { describe, it, expect, vi } from 'vitest';
const { createMainLogWriter } = require('../../main/main-log-writer.js');
function fakeFs() {
const calls = { mkdir: [], append: [] };
return {
calls,
mkdir: async (dir, opts) => { calls.mkdir.push([dir, opts]); },
appendFile: async (file, data) => { calls.append.push([file, data]); },
};
}
function fakeTimers() {
const pending = [];
return {
pending,
setTimeout: (fn, ms) => { const h = { fn, ms, unref() {} }; pending.push(h); return h; },
clearTimeout: (h) => { const i = pending.indexOf(h); if (i >= 0) pending.splice(i, 1); },
fireAll: () => { const fns = pending.splice(0); for (const h of fns) h.fn(); },
};
}
describe('createMainLogWriter —— P2-10 异步缓冲日志', () => {
it('write 只入缓冲,flush 后一把 appendFile', async () => {
const fs = fakeFs();
const w = createMainLogWriter({ logDir: '/d', logFile: '/d/a.log', fsImpl: fs });
w.write('l1\n');
w.write('l2\n');
expect(fs.calls.append).toEqual([]);
await w.flush();
expect(fs.calls.mkdir).toEqual([['/d', { recursive: true }]]);
expect(fs.calls.append).toEqual([['/d/a.log', 'l1\nl2\n']]);
});
it('flush 期间缓冲清空;空缓冲 flush 是 no-op', async () => {
const fs = fakeFs();
const w = createMainLogWriter({ logDir: '/d', logFile: '/d/a.log', fsImpl: fs });
await w.flush();
expect(fs.calls.append).toEqual([]);
w.write('x\n');
await w.flush();
await w.flush();
expect(fs.calls.append).toEqual([['/d/a.log', 'x\n']]);
});
it('mkdir 只第一次做(缓存 promise)', async () => {
const fs = fakeFs();
const w = createMainLogWriter({ logDir: '/d', logFile: '/d/a.log', fsImpl: fs });
w.write('a\n');
await w.flush();
w.write('b\n');
await w.flush();
expect(fs.calls.mkdir.length).toBe(1);
expect(fs.calls.append.length).toBe(2);
});
it('写失败静默丢弃,不抛错,缓冲不残留', async () => {
const fs = {
mkdir: async () => {},
appendFile: async () => { throw new Error('disk full'); },
};
const w = createMainLogWriter({ logDir: '/d', logFile: '/d/a.log', fsImpl: fs });
w.write('boom\n');
await expect(w.flush()).resolves.toBeUndefined();
// 缓冲已清,不会无限重试
await w.flush();
});
it('缓冲超 maxBuffered 丢最老行', async () => {
const fs = fakeFs();
const w = createMainLogWriter({
logDir: '/d', logFile: '/d/a.log', fsImpl: fs, maxBuffered: 3,
});
w.write('1\n'); w.write('2\n'); w.write('3\n'); w.write('4\n');
await w.flush();
expect(fs.calls.append).toEqual([['/d/a.log', '2\n3\n4\n']]);
});
it('write 调度一次 flush 定时器(不重复排)', async () => {
const fs = fakeFs();
const timers = fakeTimers();
const w = createMainLogWriter({
logDir: '/d', logFile: '/d/a.log', fsImpl: fs,
setTimeoutImpl: timers.setTimeout, clearTimeoutImpl: timers.clearTimeout,
flushMs: 250,
});
w.write('a\n');
w.write('b\n');
expect(timers.pending.length).toBe(1);
expect(timers.pending[0].ms).toBe(250);
timers.fireAll();
await w.flush(); // 等 in-flight 落地
expect(fs.calls.append.length).toBe(1);
expect(fs.calls.append[0][1]).toBe('a\nb\n');
});
it('dispose 取消待发 flush', () => {
const timers = fakeTimers();
const w = createMainLogWriter({
logDir: '/d', logFile: '/d/a.log', fsImpl: fakeFs(),
setTimeoutImpl: timers.setTimeout, clearTimeoutImpl: timers.clearTimeout,
});
w.write('a\n');
expect(timers.pending.length).toBe(1);
w.dispose();
expect(timers.pending.length).toBe(0);
});
it('默认 fsImpl / 定时器下 write 不抛错', () => {
const w = createMainLogWriter({ logDir: '/nonexistent-dir-x', logFile: '/nonexistent-dir-x/a.log' });
expect(() => w.write('hello\n')).not.toThrow();
w.dispose();
});
});