const fs = require('fs'); const { LOG_FILE } = require('../logger'); // Read the application/event log (JSON lines written by the Winston file transport) for the admin // Application Logs viewer. Reads the current log file, parses the most recent lines, and returns them // newest-first with optional category/level/search filtering plus the available filter facets. const SCAN_LINES = 8000; // how many trailing lines to parse (the file is size-rotated, ~5MB) function readLogs(filters = {}, limit = 500) { let lines = []; try { lines = fs.readFileSync(LOG_FILE, 'utf8').split('\n').filter(Boolean); } catch { return { logs: [], total: 0, categories: [], levels: [] }; } // Parse the trailing window, newest first. const parsed = []; for (let i = lines.length - 1, scanned = 0; i >= 0 && scanned < SCAN_LINES; i -= 1, scanned += 1) { try { const obj = JSON.parse(lines[i]); parsed.push({ timestamp: obj.timestamp || null, level: obj.level || 'info', category: obj.category || 'system', message: obj.message || '', meta: stripMeta(obj) }); } catch { // skip malformed lines } } const categories = [...new Set(parsed.map((p) => p.category))].sort(); const levels = [...new Set(parsed.map((p) => p.level))].sort(); let filtered = parsed; if (filters.category) filtered = filtered.filter((p) => p.category === filters.category); if (filters.level) filtered = filtered.filter((p) => p.level === filters.level); if (filters.q) { const needle = String(filters.q).toLowerCase(); filtered = filtered.filter((p) => `${p.message} ${JSON.stringify(p.meta)}`.toLowerCase().includes(needle)); } return { logs: filtered.slice(0, Math.min(2000, Number(limit) || 500)), total: filtered.length, categories, levels }; } // Everything on a log line except the standard fields is "meta" (the structured detail). function stripMeta(obj) { const meta = {}; for (const [key, value] of Object.entries(obj)) { if (!['timestamp', 'level', 'category', 'message'].includes(key)) meta[key] = value; } return meta; } module.exports = { readLogs };