Dev Build / cleanup (push) Waiting to run
Dev Build / typecheck (push) Waiting to run
Publish MCP to npm / Build, test, and publish MCP (push) Waiting to run
Publish SDK to npm / Build, test, and publish SDK (push) Waiting to run
Sync action catalog to docs / sync (push) Waiting to run
Publish UI to npm / Build and publish UI (push) Waiting to run
Build and Release / preflight (push) Waiting to run
100 lines
3.9 KiB
TypeScript
100 lines
3.9 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const { fileWrite } = vi.hoisted(() => ({ fileWrite: vi.fn() }));
|
|
|
|
vi.mock('../src/log-file-transport', () => ({
|
|
getFileTransport: () => ({ write: fileWrite, close: async () => {} }),
|
|
}));
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.unstubAllEnvs();
|
|
vi.resetModules();
|
|
fileWrite.mockClear();
|
|
});
|
|
|
|
async function setup() {
|
|
vi.stubEnv('SNOWLUMA_LOG_LEVEL', 'trace');
|
|
vi.stubEnv('SNOWLUMA_LOG_FILE_LEVEL', 'error');
|
|
vi.stubEnv('SNOWLUMA_TRACE_BUFFER', '100');
|
|
vi.spyOn(process.stdout, 'write').mockReturnValue(true);
|
|
vi.spyOn(process.stderr, 'write').mockReturnValue(true);
|
|
const logger = await import('../src/logger');
|
|
return { ...logger, log: logger.createLogger('Snapshot') };
|
|
}
|
|
|
|
describe('retained log snapshots', () => {
|
|
it('returns empty snapshots before any record has been emitted', async () => {
|
|
const logger = await setup();
|
|
expect(logger.getRecentLogs()).toEqual([]);
|
|
expect(logger.getLogSnapshot()).toEqual([]);
|
|
});
|
|
|
|
it('merges wrapped rings in emission order and returns only the requested tail', async () => {
|
|
const logger = await setup();
|
|
const emitted: import('../src/logger').LogEntry[] = [];
|
|
const unsubscribe = logger.subscribeLogs((entry) => emitted.push(entry));
|
|
|
|
for (let i = 0; i < 1100; i += 1) {
|
|
logger.log.info('normal %d', i);
|
|
logger.log.trace('trace %d', i);
|
|
}
|
|
|
|
unsubscribe();
|
|
const normal = emitted.filter((entry) => entry.level !== 'trace').slice(-1000);
|
|
const trace = emitted.filter((entry) => entry.level === 'trace').slice(-100);
|
|
const expected = [...normal, ...trace].sort((a, b) => a.id - b.id);
|
|
expect(logger.getLogSnapshot()).toEqual(expected);
|
|
|
|
for (const limit of [1, 2, 30, 300, 1000, 1100, 2000, 0, -1, 2.9, NaN, Infinity]) {
|
|
expect(logger.getRecentLogs(limit)).toEqual(
|
|
expected.slice(-Math.max(1, Math.trunc(limit))),
|
|
);
|
|
}
|
|
|
|
expect(logger.getRecentLogs()).toEqual(expected.slice(-300));
|
|
const snapshot = logger.getRecentLogs(5);
|
|
snapshot.pop();
|
|
expect(logger.getRecentLogs(5)).toEqual(expected.slice(-5));
|
|
});
|
|
|
|
it.each(['info', 'trace'] as const)(
|
|
'reads a tail when only the %s ring has records',
|
|
async (level) => {
|
|
const logger = await setup();
|
|
logger.log[level]('first');
|
|
logger.log[level]('second');
|
|
expect(logger.getRecentLogs(1).map((entry) => entry.message)).toEqual(['second']);
|
|
expect(logger.getLogSnapshot().map((entry) => entry.message)).toEqual([
|
|
'first',
|
|
'second',
|
|
]);
|
|
},
|
|
);
|
|
|
|
it('preserves ID gaps and file routing without publishing file-only entries', async () => {
|
|
const logger = await setup();
|
|
vi.stubEnv('SNOWLUMA_LOG_FILE_LEVEL', 'debug');
|
|
vi.resetModules();
|
|
const fresh = await import('../src/logger');
|
|
fresh.setLogLevel('info');
|
|
const entries: import('../src/logger').LogEntry[] = [];
|
|
const unsubscribe = fresh.subscribeLogs((entry) => entries.push(entry));
|
|
const log = fresh.createLogger('FileOnly').child({ uin: 10001 });
|
|
log.info('before');
|
|
fresh.runWithRequestId(42, () => log.debug('password=secret file-only\x07'));
|
|
log.info('after');
|
|
unsubscribe();
|
|
|
|
expect(entries.map((entry) => entry.message)).toEqual(['before', 'after']);
|
|
expect(entries[1]!.id).toBe(entries[0]!.id + 2);
|
|
expect(fresh.getLogSnapshot()).toEqual(entries);
|
|
expect(fileWrite).toHaveBeenCalledWith(
|
|
expect.stringMatching(/\[req#42\] password=\*\*\* file-only/),
|
|
10001,
|
|
);
|
|
expect(process.stdout.write).toHaveBeenCalledTimes(2);
|
|
expect(logger.getLogSnapshot()).toEqual([]);
|
|
});
|
|
});
|