import { getConfig, getOperatorInfo } from './config.js';
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
const LOG_LEVELS: readonly LogLevel[] = ['debug', 'info', 'warn', 'error'];
const levelIndex = new Map<LogLevel, number>(LOG_LEVELS.map((l, i) => [l, i]));
interface LogEntry {
timestamp: string;
level: LogLevel;
message: string;
operator?: { email?: string; name?: string };
context?: Record<string, unknown>;
error?: { message: string; stack?: string };
}
const shouldLog = (level: LogLevel): boolean => {
const config = getConfig();
return (levelIndex.get(level) ?? 0) >= (levelIndex.get(config.logLevel) ?? 0);
};
const createEntry = (
level: LogLevel,
message: string,
context?: Record<string, unknown>,
error?: Error
): LogEntry => {
const entry: LogEntry = {
timestamp: new Date().toISOString(),
level,
message,
operator: getOperatorInfo(),
};
if (context && Object.keys(context).length) entry.context = context;
if (error) entry.error = { message: error.message, stack: error.stack };
return entry;
};
const output = (entry: LogEntry): void => console.error(JSON.stringify(entry));
export const logger = {
debug: (message: string, context?: Record<string, unknown>): void => {
if (shouldLog('debug')) output(createEntry('debug', message, context));
},
info: (message: string, context?: Record<string, unknown>): void => {
if (shouldLog('info')) output(createEntry('info', message, context));
},
warn: (message: string, context?: Record<string, unknown>): void => {
if (shouldLog('warn')) output(createEntry('warn', message, context));
},
error: (message: string, error?: Error, context?: Record<string, unknown>): void => {
if (shouldLog('error')) output(createEntry('error', message, context, error));
},
command: (action: 'plan' | 'execute', command: string, result: 'success' | 'failure', context?: Record<string, unknown>): void => {
output(createEntry('info', `Command ${action}: ${result}`, { ...context, command, action, result }));
},
};
export default logger;