/** * LandveX Finance — Strukturerad JSON-loggning * Sprint 3: Observability */ const isDev = process.env.NODE_ENV === 'development'; const LOG_LEVEL = process.env.LOG_LEVEL || 'info'; const LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }; function shouldLog(level) { return LEVELS[level] >= LEVELS[LOG_LEVEL]; } /** * Huvudloggningsfunktion * @param {string} level - debug|info|warn|error * @param {string} event - Event-namn (snake_case) * @param {object} meta - Metadata */ export function logFinance(level, event, meta = {}) { if (!shouldLog(level)) return; const entry = { ts: new Date().toISOString(), svc: 'finance', lvl: level.toUpperCase(), evt: event, tid: meta.tenant || meta.tid || 'unknown', uid: meta.user || meta.uid || 'anonymous', dur_ms: meta.duration || meta.dur_ms, trace_id: meta.traceId || meta.trace_id, ...meta.context, }; // Fel-objekt if (meta.error || meta.err) { const err = meta.error || meta.err; entry.err = { msg: err.message, code: err.code || 'UNKNOWN', stack: isDev ? err.stack : undefined, }; } // Rensa undefined-värden Object.keys(entry).forEach(k => { if (entry[k] === undefined) delete entry[k]; }); // Output const line = JSON.stringify(entry); if (level === 'error') { console.error(line); } else if (level === 'warn') { console.warn(line); } else { console.log(line); } } // Convenience wrappers export const financeDebug = (evt, meta) => logFinance('debug', evt, meta); export const financeInfo = (evt, meta) => logFinance('info', evt, meta); export const financeWarn = (evt, meta) => logFinance('warn', evt, meta); export const financeError = (evt, meta) => logFinance('error', evt, meta); // Request-logger middleware export function requestLogger(req, res, next) { const start = Date.now(); const tid = req.headers['x-tenant-id'] || 'unknown'; const uid = req.user?.email || req.headers['x-user-id'] || 'anonymous'; res.on('finish', () => { const duration = Date.now() - start; const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info'; logFinance(level, 'http_request', { tenant: tid, user: uid, duration, context: { method: req.method, path: req.path, route: req.route?.path, status: res.statusCode, content_length: res.get('content-length'), user_agent: req.get('user-agent'), ip: req.ip, }, }); }); next(); }