UNPKG

nsyslog

Version:

Modular new generation log agent. Reads, transform, aggregate, correlate and send logs from sources to destinations

213 lines (187 loc) 4.95 kB
const cluster = require('./'), worker = require('./flow-worker'), logger = require('../logger'), Semaphore = require('../semaphore'), Duplex = require('stream').Duplex, Stats = require('../stats'), jsexpr = require('jsexpr'), hash = require('string-hash'), {CMD,MODULE} = worker; const ISDEBUG = typeof v8debug === 'object' || /--debug|--inspect/.test(process.execArgv.join(' ')), SCRIPT = `${__dirname}/flow-worker`, HWM = 1000, MODE = { all:"all", balanced:"balanced", roundrobin:"roundrobin", expression:"expression" }; function forkChild(cfg,flow) { let child = cluster.fork(SCRIPT,['--flow',flow], ISDEBUG? {execArgv:['--inspect=0']}:null); child.ready = new Promise(async(resolve,reject)=>{ function hdl(child,module,msg) { if(msg.cmd==CMD.online) { child.send({module,cmd:CMD.start,cfg,flow}); } else if(msg.cmd==CMD.success) { logger.info(`Child ${child.pid} started.`); cluster.off(module,child,hdl); resolve(); } else { cluster.off(module,child,hdl); logger.warn('Unknown child message',msg); //reject(msg.error || msg); } } cluster.on(MODULE,child,hdl); }); return child; } function handleWorker(w) { let child = w.child, sem = w.sem; child.on('error',logger.error.bind(logger)); // If worker process fails, restart child.on('close',code=>{ // Worker is closed by nsyslog if(w.closed) { logger.info(`Child worker ${w.child.pid} shut down`); } // Worker has crashed. Restart else { logger.warn(`Child worker ${w.child.pid} closed with code: ${code}. Restarting...`); } }); cluster.on(MODULE,child,(child,module,msg)=>{ let cid = msg.cid, hasCid = (cid!==undefined); if(msg.cmd == CMD.error && !hasCid) { logger.error(msg.error); } else if(msg.cmd == CMD.ack && hasCid) { sem.leave(); } else if(msg.cmd == CMD.error && hasCid) { sem.leave(); } else if(msg.cmd == CMD.stats) { Stats.fetch('main').merge(msg.stats); } else if(msg.cmd == CMD.event) { w.str.emit(msg.event,msg.args[3],{ stage : msg.args[0], flow : msg.args[1], module : {instance:{id:msg.args[2]}} }); } else { logger.debug(msg); } }); } function sendEntry(entry,w) { return new Promise(async(ok,rej)=>{ let msg = {module:MODULE, cmd:CMD.emit, entry, cid:w.cid++}; try { await Promise.all([w.sem.take(),w.child.ready]); }catch(err) { logger.error(err); return rej('Child process is not ready'); } w.child.send(msg,ok); }); } function forkFlow(cfg,flowId,cores,mode) { let workers = []; let expr = null; let lidx = 0; cores = cores || 1; if(mode!==true && !MODE[mode]) { expr = jsexpr.expression(mode||'true'); mode = MODE.expression; } else { mode = MODE[mode] || MODE.roundrobin; } let str = new Duplex({ objectMode : true, highWaterMark : HWM, async write(entry,encoding,callback) { let pr = null, w = null; // Send entry to forked flows switch(mode) { // Mode 'all' : Send to all children case MODE.all : pr = Promise.all(workers.map(w=>sendEntry(entry,w))); break; // Mode 'expression' : Send to child based on expression eval hash case MODE.expression : let idx = hash(expr(entry)) % cores; w = workers[idx]; pr = sendEntry(entry,w); break; // Mode 'balanced' : Send to less overloaded child case MODE.balanced : let min = Number.POSITIVE_INFINITY; let midx = -1; for(let i=0;i<cores;i++) { let nw = workers[i]; if(nw.sem.current<min) { min = nw.sem.current; midx = i; } } w = workers[midx]; pr = sendEntry(entry,w); break; // Mode 'roundrobin' case MODE.roundrobin : default : w = workers[lidx]; lidx = (lidx+1)%cores; pr = sendEntry(entry,w); break; } try { await pr; callback(); }catch(err) { logger.error(err); callback(); } }, read() {} }); str.instance = {id:`fork_${flowId}`}; str.closefork = async function() { await Promise.all(workers.map(w=>{ logger.info(`Closing worker process ${w.child.pid}`); w.child.send({module:MODULE,cmd:CMD.stop},()=>{ w.closed = true; }); return new Promise(ok=>setTimeout(ok,2000)); })); }; for(let i=0;i<cores;i++) { let w = { cfg, flowId, str, cid: 0, sem : new Semaphore(HWM), child : forkChild(cfg,flowId) }; var fnclose = ()=>{ setTimeout(()=>{ w.sem.destroy(); w.sem = new Semaphore(HWM); w.child = forkChild(cfg,flowId); w.child.on('close',()=>fnclose()); handleWorker(w); },5000); } w.child.on('close',()=>fnclose()); handleWorker(w); workers.push(w); } return str; } module.exports = {fork:forkFlow,MODULE};