Files
nanoclaw/src/logger.ts
gavrielc 7e7492ebba style: apply prettier formatting to logger
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 15:13:39 +03:00

84 lines
2.7 KiB
TypeScript

const LEVELS = { debug: 20, info: 30, warn: 40, error: 50, fatal: 60 } as const;
type Level = keyof typeof LEVELS;
const COLORS: Record<Level, string> = {
debug: '\x1b[34m',
info: '\x1b[32m',
warn: '\x1b[33m',
error: '\x1b[31m',
fatal: '\x1b[41m\x1b[37m',
};
const KEY_COLOR = '\x1b[35m';
const MSG_COLOR = '\x1b[36m';
const RESET = '\x1b[39m';
const FULL_RESET = '\x1b[0m';
const threshold =
LEVELS[(process.env.LOG_LEVEL as Level) || 'info'] ?? LEVELS.info;
function formatErr(err: unknown): string {
if (err instanceof Error) {
return `{\n "type": "${err.constructor.name}",\n "message": "${err.message}",\n "stack":\n ${err.stack}\n }`;
}
return JSON.stringify(err);
}
function formatData(data: Record<string, unknown>): string {
let out = '';
for (const [k, v] of Object.entries(data)) {
if (k === 'err') {
out += `\n ${KEY_COLOR}err${RESET}: ${formatErr(v)}`;
} else {
out += `\n ${KEY_COLOR}${k}${RESET}: ${JSON.stringify(v)}`;
}
}
return out;
}
function ts(): string {
const d = new Date();
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}.${String(d.getMilliseconds()).padStart(3, '0')}`;
}
function log(
level: Level,
dataOrMsg: Record<string, unknown> | string,
msg?: string,
): void {
if (LEVELS[level] < threshold) return;
const tag = `${COLORS[level]}${level.toUpperCase()}${level === 'fatal' ? FULL_RESET : RESET}`;
const stream = LEVELS[level] >= LEVELS.warn ? process.stderr : process.stdout;
if (typeof dataOrMsg === 'string') {
stream.write(
`[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${dataOrMsg}${RESET}\n`,
);
} else {
stream.write(
`[${ts()}] ${tag} (${process.pid}): ${MSG_COLOR}${msg}${RESET}${formatData(dataOrMsg)}\n`,
);
}
}
export const logger = {
debug: (dataOrMsg: Record<string, unknown> | string, msg?: string) =>
log('debug', dataOrMsg, msg),
info: (dataOrMsg: Record<string, unknown> | string, msg?: string) =>
log('info', dataOrMsg, msg),
warn: (dataOrMsg: Record<string, unknown> | string, msg?: string) =>
log('warn', dataOrMsg, msg),
error: (dataOrMsg: Record<string, unknown> | string, msg?: string) =>
log('error', dataOrMsg, msg),
fatal: (dataOrMsg: Record<string, unknown> | string, msg?: string) =>
log('fatal', dataOrMsg, msg),
};
// Route uncaught errors through logger so they get timestamps in stderr
process.on('uncaughtException', (err) => {
logger.fatal({ err }, 'Uncaught exception');
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
logger.error({ err: reason }, 'Unhandled rejection');
});