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>
60 lines
1.6 KiB
TypeScript
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;
|
|
}
|