/** * Tiny structured logger (JSON lines optional). */ const LEVELS = { debug: 10, info: 20, warn: 30, error: 40 } const minLevel = LEVELS[String(process.env.LOG_LEVEL || 'info').toLowerCase()] ?? LEVELS.info function emit(level, scope, message, fields) { if ((LEVELS[level] ?? 99) < minLevel) return const line = { ts: new Date().toISOString(), level, scope, msg: message, ...(fields && typeof fields === 'object' ? fields : {}), } const text = process.env.LOG_JSON === '1' ? JSON.stringify(line) : formatPretty(line) if (level === 'error') console.error(text) else if (level === 'warn') console.warn(text) else console.log(text) } function formatPretty(line) { const extra = { ...line } delete extra.ts delete extra.level delete extra.scope delete extra.msg const keys = Object.keys(extra) const tail = keys.length ? ' ' + JSON.stringify(extra) : '' return `${line.ts} [${line.level}] ${line.scope}: ${line.msg}${tail}` } function child(scope) { return { debug: (msg, fields) => emit('debug', scope, msg, fields), info: (msg, fields) => emit('info', scope, msg, fields), warn: (msg, fields) => emit('warn', scope, msg, fields), error: (msg, fields) => emit('error', scope, msg, fields), child: (sub) => child(`${scope}:${sub}`), banner(fields = {}) { const bar = '═'.repeat(56) console.log(bar) console.log(` ${fields.title || 'Pear App Server'}`) for (const [k, v] of Object.entries(fields)) { if (k === 'title') continue console.log(` ${k}: ${v}`) } console.log(bar) }, } } const logger = child('app') export default logger