87 lines
2.5 KiB
JavaScript
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 }
|