import type { Database } from '@db/sqlite'; import * as colors from 'jsr:@std/fmt@^1.0.4/colors'; import * as log from 'jsr:@std/log'; import { DatabaseHandler } from './databaseLogger.ts'; import { getEveFilePath } from './files.ts'; export * as log from 'jsr:@std/log'; /** * Sanitizes data before logging to prevent accidental exposure of sensitive information * @param data The data to sanitize * @returns Sanitized data safe for logging */ function sanitizeForLogging(data: unknown): unknown { if (data === null || data === undefined || typeof data !== 'object') { return data; } if (data instanceof Uint8Array) { // Never log raw binary data that could contain keys return `[Uint8Array length=${data.length}]`; } if (Array.isArray(data)) { return data.map(sanitizeForLogging); } const sanitized: Record<string, unknown> = {}; const sensitiveKeys = [ 'privatekey', 'private_key', 'privkey', 'priv_key', 'secretkey', 'secret_key', 'seckey', 'sec_key', 'password', 'pass', 'pwd', 'token', 'auth', 'ccnprivatekey', 'ccn_private_key', 'ccnprivkey', 'seed', 'seedphrase', 'seed_phrase', 'mnemonic', 'mnemonic_phrase', 'mnemonic_phrase_words', ]; for (const [key, value] of Object.entries(data as Record<string, unknown>)) { const lowerKey = key.toLowerCase(); if (sensitiveKeys.some((sensitiveKey) => lowerKey.includes(sensitiveKey))) { sanitized[key] = '[REDACTED]'; } else { sanitized[key] = sanitizeForLogging(value); } } return sanitized; } export async function setupLogger(db: Database | null) { const formatLevel = (level: number): string => { return ( { 10: colors.gray('[DEBUG]'), 20: colors.green('[INFO] '), 30: colors.yellow('[WARN] '), 40: colors.red('[ERROR]'), 50: colors.bgRed('[FATAL]'), }[level] || `[LVL${level}]` ); }; const levelName = (level: number): string => { return ( { 10: 'DEBUG', 20: 'INFO', 30: 'WARN', 40: 'ERROR', 50: 'FATAL', }[level] || `LVL${level}` ); }; const formatArg = (arg: unknown): string => { const sanitized = sanitizeForLogging(arg); if (typeof sanitized === 'object') return JSON.stringify(sanitized); return String(sanitized); }; const handlers: Record<string, log.BaseHandler> = { console: new log.ConsoleHandler('DEBUG', { useColors: true, formatter: (record) => { const timestamp = new Date().toISOString(); let msg = `${colors.dim(`[${timestamp}]`)} ${formatLevel(record.level)} ${record.msg}`; if (record.args.length > 0) { const args = record.args .map((arg, i) => `${colors.dim(`arg${i}:`)} ${formatArg(arg)}`) .join(' '); msg += ` ${colors.dim('|')} ${args}`; } return msg; }, }), file: new log.FileHandler('DEBUG', { filename: Deno.env.get('LOG_FILE') || (await getEveFilePath('eve-logs.jsonl')), formatter: (record) => { const timestamp = new Date().toISOString(); return JSON.stringify({ timestamp, level: levelName(record.level), msg: record.msg, args: record.args.map(sanitizeForLogging), }); }, }), }; if (db) { handlers.database = new DatabaseHandler('DEBUG', { db }); } log.setup({ handlers, loggers: { default: { level: 'DEBUG', handlers: ['console', 'file', 'database'], }, }, }); }