Files
golfgame/tests/soak/core/logger.ts
adlee-was-taken 066e482f06 feat(soak): structured JSONL logger with child contexts
Single file, no transport, writes one JSON line per call to stdout.
Child loggers inherit parent meta so scenarios can bind room/game
context once and forget about it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 17:12:27 -04:00

60 lines
1.6 KiB
TypeScript

/**
* Structured JSONL logger for the soak harness.
*
* One JSON line per call, written to stdout by default. Child loggers
* inherit parent meta so scenarios can bind room/game context once and
* every subsequent call carries it automatically.
*/
import type { Logger, LogLevel } from './types';
const LEVEL_ORDER: Record<LogLevel, number> = {
debug: 0,
info: 1,
warn: 2,
error: 3,
};
export interface LoggerOptions {
runId: string;
minLevel?: LogLevel;
/** Defaults to process.stdout.write bound to stdout. Override for tests. */
write?: (line: string) => boolean;
baseMeta?: Record<string, unknown>;
}
export function createLogger(opts: LoggerOptions): Logger {
const minLevel = opts.minLevel ?? 'info';
const write = opts.write ?? ((s: string) => process.stdout.write(s));
const baseMeta = opts.baseMeta ?? {};
function emit(level: LogLevel, msg: string, meta?: object): void {
if (LEVEL_ORDER[level] < LEVEL_ORDER[minLevel]) return;
const line = JSON.stringify({
timestamp: new Date().toISOString(),
level,
msg,
runId: opts.runId,
...baseMeta,
...(meta ?? {}),
}) + '\n';
write(line);
}
const logger: Logger = {
debug: (msg, meta) => emit('debug', msg, meta),
info: (msg, meta) => emit('info', msg, meta),
warn: (msg, meta) => emit('warn', msg, meta),
error: (msg, meta) => emit('error', msg, meta),
child: (meta) =>
createLogger({
runId: opts.runId,
minLevel,
write,
baseMeta: { ...baseMeta, ...meta },
}),
};
return logger;
}