Files
Toolbox/src/server/logger.js
T

87 lines
2.5 KiB
JavaScript

const fs = require('fs')
const path = require('path')
const logsDir = path.join(process.cwd(), 'logs')
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true })
const logFile = path.join(logsDir, 'app.log')
if (!fs.existsSync(logFile)) fs.writeFileSync(logFile, '')
const channelFileCache = new Map()
let pendingWrites = new Map()
let flushScheduled = false
const ts = () => new Date().toISOString()
const ensureChannel = channel => {
const name = String(channel || '').trim()
if (!name) return null
if (channelFileCache.has(name)) return channelFileCache.get(name)
const file = path.join(logsDir, `${name}.log`)
if (!fs.existsSync(file)) fs.writeFileSync(file, '')
channelFileCache.set(name, file)
return file
}
const flushPendingWrites = () => {
flushScheduled = false
if (pendingWrites.size === 0) return
const batch = pendingWrites
pendingWrites = new Map()
batch.forEach((content, file) => {
fs.appendFile(file, content, () => {})
})
if (pendingWrites.size > 0) scheduleFlush()
}
const scheduleFlush = () => {
if (flushScheduled) return
flushScheduled = true
setImmediate(flushPendingWrites)
}
const enqueueWrite = (file, line) => {
const prev = pendingWrites.get(file) || ''
pendingWrites.set(file, prev + line)
scheduleFlush()
}
const log = (msg, channel) => {
const line = `${ts()} ${String(msg)}\n`
enqueueWrite(logFile, line)
const chFile = ensureChannel(channel)
if (chFile) enqueueWrite(chFile, line)
}
const logJSON = (label, obj, channel) => {
let s = ''
try { s = JSON.stringify(obj) } catch { s = String(obj) }
log(`${label} ${s}`, channel)
}
const getLogs = (limit = 2000, channel) => {
let file = logFile
const chFile = ensureChannel(channel)
if (chFile) file = chFile
let content = ''
try {
const stat = fs.statSync(file)
const maxReadBytes = Math.min(stat.size, 512 * 1024)
const start = Math.max(0, stat.size - maxReadBytes)
const fd = fs.openSync(file, 'r')
const buf = Buffer.alloc(maxReadBytes)
const bytesRead = fs.readSync(fd, buf, 0, maxReadBytes, start)
fs.closeSync(fd)
content = buf.subarray(0, bytesRead).toString('utf-8')
} catch {}
const lines = content.split(/\r?\n/).filter(Boolean)
const tail = lines.slice(Math.max(0, lines.length - limit))
return tail
}
const clearLogs = channel => {
const chFile = ensureChannel(channel)
if (chFile) fs.truncateSync(chFile, 0)
else fs.truncateSync(logFile, 0)
}
module.exports = { log, logJSON, getLogs, clearLogs }