feat: add LogService with ring buffer and subscriber push

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-10 02:17:48 +00:00
parent 778eeb3832
commit f8cb83481e
2 changed files with 77 additions and 0 deletions
@@ -0,0 +1,40 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { createLogService } from '../logService.js';
describe('LogService', () => {
let logService: ReturnType<typeof createLogService>;
beforeEach(() => {
logService = createLogService();
});
it('logs entries and returns them via getRecent', () => {
logService.log('info', 'Game', 'Server started');
const entries = logService.getRecent();
expect(entries).toHaveLength(1);
expect(entries[0].severity).toBe('info');
expect(entries[0].category).toBe('Game');
expect(entries[0].message).toBe('Server started');
expect(entries[0].timestamp).toBeGreaterThan(0);
});
it('enforces ring buffer limit of 200', () => {
for (let i = 0; i < 210; i++) {
logService.log('info', 'Game', `Entry ${i}`);
}
const entries = logService.getRecent();
expect(entries).toHaveLength(200);
expect(entries[0].message).toBe('Entry 10');
});
it('notifies subscribers on new entry', () => {
const received: any[] = [];
const unsub = logService.subscribe((entry) => received.push(entry));
logService.log('error', 'LLM', 'Timeout');
expect(received).toHaveLength(1);
expect(received[0].severity).toBe('error');
unsub();
logService.log('info', 'Game', 'Ignored');
expect(received).toHaveLength(1);
});
});
+37
View File
@@ -0,0 +1,37 @@
import type { LogEntry, LogSeverity, LogCategory } from '@dflike/shared';
const MAX_ENTRIES = 200;
export function createLogService() {
const entries: LogEntry[] = [];
const subscribers = new Set<(entry: LogEntry) => void>();
return {
log(severity: LogSeverity, category: LogCategory, message: string): void {
const entry: LogEntry = {
timestamp: Date.now(),
severity,
category,
message,
};
entries.push(entry);
if (entries.length > MAX_ENTRIES) {
entries.shift();
}
for (const cb of subscribers) {
cb(entry);
}
},
getRecent(): LogEntry[] {
return [...entries];
},
subscribe(cb: (entry: LogEntry) => void): () => void {
subscribers.add(cb);
return () => { subscribers.delete(cb); };
},
};
}
export type LogService = ReturnType<typeof createLogService>;