Files
nanoclaw/src/log.ts
gavrielc 3f0451b7b0 v2 phase 1: foundation — types, DB layer, logging
Add the v2 data layer: typed interfaces, central DB with migration
runner, per-entity CRUD, and agent-runner session DB operations.

- src/log.ts: concise message-first logging API
- src/types-v2.ts: AgentGroup, MessagingGroup, Session, MessageIn/Out
- src/db/: connection (WAL), migration runner, 001-initial schema,
  CRUD for agent_groups, messaging_groups, sessions, pending_questions
- container/agent-runner/src/db/: session DB connection, messages_in
  reads + status transitions, messages_out writes
- 31 new tests, all 277 tests pass

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 23:34:09 +03:00

65 lines
2.3 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 `{ type: "${err.constructor.name}", message: "${err.message}", stack: ${err.stack} }`;
}
return JSON.stringify(err);
}
function formatData(data: Record<string, unknown>): string {
const parts: string[] = [];
for (const [k, v] of Object.entries(data)) {
parts.push(`${KEY_COLOR}${k}${RESET}=${k === 'err' ? formatErr(v) : JSON.stringify(v)}`);
}
return parts.length ? ' ' + parts.join(' ') : '';
}
function ts(): string {
const d = new Date();
const hh = String(d.getHours()).padStart(2, '0');
const mm = String(d.getMinutes()).padStart(2, '0');
const ss = String(d.getSeconds()).padStart(2, '0');
const ms = String(d.getMilliseconds()).padStart(3, '0');
return `${hh}:${mm}:${ss}.${ms}`;
}
function emit(level: Level, msg: string, data?: Record<string, unknown>): 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;
stream.write(`[${ts()}] ${tag} ${MSG_COLOR}${msg}${RESET}${data ? formatData(data) : ''}\n`);
}
export const log = {
debug: (msg: string, data?: Record<string, unknown>) => emit('debug', msg, data),
info: (msg: string, data?: Record<string, unknown>) => emit('info', msg, data),
warn: (msg: string, data?: Record<string, unknown>) => emit('warn', msg, data),
error: (msg: string, data?: Record<string, unknown>) => emit('error', msg, data),
fatal: (msg: string, data?: Record<string, unknown>) => emit('fatal', msg, data),
};
process.on('uncaughtException', (err) => {
log.fatal('Uncaught exception', { err });
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
log.error('Unhandled rejection', { err: reason });
});