// ============================================
// Logger — structured logging with pino
// Uses pino (bundled with Fastify) + multistream (stdout + file)
// Rotation handled by pm2-logrotate
// ============================================
const pino = require('pino')
const path = require('path')
const fs = require('fs')
const LOG_DIR = path.resolve(__dirname, '..', 'logs')
const LOG_LEVEL = (process.env.LOG_LEVEL || 'info').toLowerCase()
// Ensure logs directory exists
if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true })
// File destination (sync write — reliable, no worker thread flush issues)
const fileDest = pino.destination({
dest: path.join(LOG_DIR, 'server.log'),
sync: false, // async for performance
minLength: 0, // flush immediately (no buffering)
})
// Multistream: stdout (for PM2) + file (for structured search)
const streams = [
{ level: LOG_LEVEL, stream: process.stdout },
{ level: 'debug', stream: fileDest },
]
const rootLogger = pino({ level: 'debug' }, pino.multistream(streams))
// Flush file on exit
const flushAndExit = () => { fileDest.flushSync(); process.exit() }
process.once('SIGTERM', flushAndExit)
process.once('SIGINT', flushAndExit)
// Registry for runtime level changes
const childLoggers = new Map()
/**
* Create a child logger for a module
* @param {string} module - Module name (e.g. 'signals', 'ws', 'state')
* @returns {pino.Logger} Child logger with { module } field
*/
function createLogger(module) {
const envKey = `LOG_LEVEL_${module.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`
const level = (process.env[envKey] || LOG_LEVEL).toLowerCase()
const child = rootLogger.child({ module })
child.level = level
childLoggers.set(module, child)
return child
}
/**
* Change log level at runtime (no restart needed)
* @param {string} module - Module name or '*' for all
* @param {string} level - pino level: 'debug', 'info', 'warn', 'error', 'fatal'
*/
function setLevel(module, level) {
if (module === '*') {
for (const [, child] of childLoggers) child.level = level
return { changed: childLoggers.size }
}
const child = childLoggers.get(module)
if (child) {
child.level = level
return { changed: 1 }
}
return { changed: 0, error: `Module '${module}' not found` }
}
/**
* Get current log levels for all modules
*/
function getLevels() {
const result = {}
for (const [name, child] of childLoggers) {
result[name] = child.level
}
return result
}
module.exports = { createLogger, setLevel, getLevels }
📜 Git History
6d27024feat: structured pino logging + revert resync queue to simple handler8 weeks ago
Show last diff
Loading...