/** * Serviço de logging estruturado. * * Substitui console.log por logs com níveis, timestamps ISO e saída * simultânea para stdout (desenvolvimento) e arquivo (produção). * * Uso: * const log = require('../services/logger'); * log.info('chat', 'Mensagem enviada', { conversaId: 123 }); * log.warn('webhook', 'Falha ao baixar mídia', { erro: err.message }); * log.error('evolution', 'Timeout', err); */ const fs = require('fs'); const path = require('path'); const LOG_DIR = path.join(__dirname, '../../logs'); if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true }); const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; const currentLevel = process.env.LOG_LEVEL ? (LEVELS[process.env.LOG_LEVEL] ?? 1) : 1; function formatar(level, modulo, mensagem, extra) { const ts = new Date().toISOString(); let linha = `[${ts}] [${level.toUpperCase()}] [${modulo}] ${mensagem}`; if (extra !== undefined) { if (extra instanceof Error) { linha += ' | ' + extra.message; if (extra.stack && level === 'error') linha += '\n' + extra.stack; } else if (typeof extra === 'object') { linha += ' | ' + JSON.stringify(extra); } else { linha += ' | ' + String(extra); } } return linha; } function escrever(level, modulo, mensagem, extra) { if (LEVELS[level] < currentLevel) return; const linha = formatar(level, modulo, mensagem, extra); // Stdout (colorido se terminal interativo) const cores = { error: '\x1b[31m', warn: '\x1b[33m', info: '\x1b[36m', debug: '\x1b[90m' }; const reset = '\x1b[0m'; if (process.stdout.isTTY) { console.log(cores[level] + linha + reset); } else { console.log(linha); } // Arquivo (sempre, sem cores) try { const hoje = new Date().toISOString().slice(0, 10); fs.appendFileSync(path.join(LOG_DIR, `app_${hoje}.log`), linha + '\n', 'utf8'); } catch (_) {} } module.exports = { debug: (mod, msg, extra) => escrever('debug', mod, msg, extra), info: (mod, msg, extra) => escrever('info', mod, msg, extra), warn: (mod, msg, extra) => escrever('warn', mod, msg, extra), error: (mod, msg, extra) => escrever('error', mod, msg, extra), };