Добавлен модуль AsyncExit для выполненния cleanup-процедур перед выходом из приложения

This commit is contained in:
Book Pauk
2021-11-24 14:13:13 +07:00
parent b1e3d33694
commit 4852c7aec3
5 changed files with 179 additions and 73 deletions

View File

@@ -7,10 +7,14 @@ let instance = null;
class AppLogger { class AppLogger {
constructor() { constructor() {
if (!instance) { if (!instance) {
this.inited = false;
this.logFileName = '';
this.errLogFileName = '';
this.fatalLogFileName = '';
instance = this; instance = this;
} }
this.inited = false;
return instance; return instance;
} }
@@ -22,11 +26,16 @@ class AppLogger {
if (config.loggingEnabled) { if (config.loggingEnabled) {
await fs.ensureDir(config.logDir); await fs.ensureDir(config.logDir);
this.logFileName = `${config.logDir}/${config.name}.log`;
this.errLogFileName = `${config.logDir}/${config.name}.err.log`;
this.fatalLogFileName = `${config.logDir}/${config.name}.fatal.log`;
loggerParams = [ loggerParams = [
{log: 'ConsoleLog'}, {log: 'ConsoleLog'},
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.log`}, {log: 'FileLog', fileName: this.logFileName},
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.err.log`, exclude: [LM_OK, LM_INFO, LM_TOTAL]}, {log: 'FileLog', fileName: this.errLogFileName, exclude: [LM_OK, LM_INFO, LM_TOTAL]},
{log: 'FileLog', fileName: `${config.logDir}/${config.name}.fatal.log`, exclude: [LM_OK, LM_INFO, LM_WARN, LM_ERR, LM_TOTAL]},//LM_FATAL only {log: 'FileLog', fileName: this.fatalLogFileName, exclude: [LM_OK, LM_INFO, LM_WARN, LM_ERR, LM_TOTAL]},//LM_FATAL only
]; ];
} }

111
server/core/AsyncExit.js Normal file
View File

@@ -0,0 +1,111 @@
let instance = null;
const defaultTimeout = 15*1000;//15 sec
const exitSignals = ['SIGINT', 'SIGTERM', 'SIGBREAK', 'SIGHUP', 'uncaughtException'];
//singleton
class AsyncExit {
constructor() {
if (!instance) {
this.onSignalCallbacks = new Map();
this.callbacks = new Map();
this.afterCallbacks = new Map();
this.exitTimeout = defaultTimeout;
this.inited = false;
instance = this;
}
return instance;
}
init(signals = null, codeOnSignal = 2) {
if (this.inited)
throw new Error('AsyncExit: initialized already');
if (!signals)
signals = exitSignals;
const runSingalCallbacks = async(signal) => {
for (const signalCallback of this.onSignalCallbacks.keys()) {
try {
await signalCallback(signal);
} catch(e) {
console.error(e);
}
}
};
for (const signal of signals) {
process.once(signal, async() => {
await runSingalCallbacks(signal);
this.exit(codeOnSignal);
});
}
this.inited = true;
}
onSignal(signalCallback) {
if (!this.onSignalCallbacks.has(signalCallback)) {
this.onSignalCallbacks.set(signalCallback, true);
}
}
add(exitCallback) {
if (!this.callbacks.has(exitCallback)) {
this.callbacks.set(exitCallback, true);
}
}
addAfter(exitCallback) {
if (!this.afterCallbacks.has(exitCallback)) {
this.afterCallbacks.set(exitCallback, true);
}
}
remove(exitCallback) {
if (this.callbacks.has(exitCallback)) {
this.callbacks.delete(exitCallback);
}
if (this.afterCallbacks.has(exitCallback)) {
this.afterCallbacks.delete(exitCallback);
}
}
setExitTimeout(timeout) {
this.exitTimeout = timeout;
}
exit(code = 0) {
if (this.exiting)
return;
this.exiting = true;
const timer = setTimeout(() => { process.exit(code); }, this.exitTimeout);
(async() => {
for (const exitCallback of this.callbacks.keys()) {
try {
await exitCallback();
} catch(e) {
console.error(e);
}
}
for (const exitCallback of this.afterCallbacks.keys()) {
try {
await exitCallback();
} catch(e) {
console.error(e);
}
}
clearTimeout(timer);
//console.log('Exited gracefully');
process.exit(code);
})();
}
}
module.exports = AsyncExit;

View File

@@ -25,7 +25,6 @@ class MegaStorage {
this.debouncedSaveStats = _.debounce(() => { this.debouncedSaveStats = _.debounce(() => {
this.saveStats().catch((e) => { this.saveStats().catch((e) => {
log(LM_ERR, `MegaStorage::saveStats ${e.message}`); log(LM_ERR, `MegaStorage::saveStats ${e.message}`);
//process.exit(1);
}); });
}, 5000, {'maxWait':6000}); }, 5000, {'maxWait':6000});

View File

@@ -2,6 +2,9 @@
Журналирование с буферизацией вывода Журналирование с буферизацией вывода
*/ */
const fs = require('fs-extra'); const fs = require('fs-extra');
const ayncExit = new (require('./AsyncExit'))();
const sleep = (ms) => { return new Promise(resolve => setTimeout(resolve, ms)) };
global.LM_OK = 0; global.LM_OK = 0;
global.LM_INFO = 1; global.LM_INFO = 1;
@@ -46,12 +49,13 @@ class BaseLog {
this.outputBuffer = []; this.outputBuffer = [];
await this.flushImpl(this.data) await this.flushImpl(this.data)
.catch(e => { console.log(e); process.exit(1); } ); .catch(e => { console.log(e); ayncExit.exit(1); } );
this.flushing = false; this.flushing = false;
} }
log(msgType, message) { log(msgType, message) {
if (this.closed) { console.log(`Logger fatal error: log was closed (message to log: ${message}})`); process.exit(1); } if (this.closed)
return;
if (!this.exclude.has(msgType)) { if (!this.exclude.has(msgType)) {
this.outputBuffer.push(message); this.outputBuffer.push(message);
@@ -73,7 +77,7 @@ class BaseLog {
} }
} }
close() { async close() {
if (this.closed) if (this.closed)
return; return;
@@ -81,12 +85,13 @@ class BaseLog {
clearInterval(this.iid); clearInterval(this.iid);
try { try {
if (this.flushing) while (this.outputBufferLength) {
this.flushImplSync(this.data); await this.flush();
this.flushImplSync(this.outputBuffer); await sleep(1);
}
} catch(e) { } catch(e) {
console.log(e); console.log(e);
process.exit(1); ayncExit.exit(1);
} }
this.outputBufferLength = 0; this.outputBufferLength = 0;
this.outputBuffer = []; this.outputBuffer = [];
@@ -103,12 +108,14 @@ class FileLog extends BaseLog {
this.rcid = 0; this.rcid = 0;
} }
close() { async close() {
if (this.closed) if (this.closed)
return; return;
super.close(); await super.close();
if (this.fd) if (this.fd) {
fs.closeSync(this.fd); await fs.close(this.fd);
this.fd = null;
}
if (this.rcid) if (this.rcid)
clearTimeout(this.rcid); clearTimeout(this.rcid);
} }
@@ -151,23 +158,15 @@ class FileLog extends BaseLog {
}, LOG_ROTATE_FILE_CHECK_INTERVAL); }, LOG_ROTATE_FILE_CHECK_INTERVAL);
} }
if (this.fd)
await fs.write(this.fd, Buffer.from(data.join(''))); await fs.write(this.fd, Buffer.from(data.join('')));
} }
flushImplSync(data) {
fs.writeSync(this.fd, Buffer.from(data.join('')));
}
} }
class ConsoleLog extends BaseLog { class ConsoleLog extends BaseLog {
async flushImpl(data) { async flushImpl(data) {
process.stdout.write(data.join('')); process.stdout.write(data.join(''));
} }
flushImplSync(data) {
process.stdout.write(data.join(''));
}
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
@@ -178,7 +177,7 @@ const factory = {
class Logger { class Logger {
constructor(params = null, cleanupCallback = null) { constructor(params = null) {
this.handlers = []; this.handlers = [];
if (params) { if (params) {
params.forEach((logParams) => { params.forEach((logParams) => {
@@ -187,12 +186,22 @@ class Logger {
this.handlers.push(new loggerClass(logParams)); this.handlers.push(new loggerClass(logParams));
}); });
} }
cleanupCallback = cleanupCallback || (() => {});
this.cleanup(cleanupCallback); this.closed = false;
ayncExit.onSignal((signal) => {
this.log(LM_FATAL, `Signal ${signal} received, exiting...`);
});
ayncExit.addAfter(this.close.bind(this));
}
formatDate(date) {
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ` +
`${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}:${date.getSeconds().toString().padStart(2, '0')}.` +
`${date.getMilliseconds().toString().padStart(3, '0')}`;
} }
prepareMessage(msgType, message) { prepareMessage(msgType, message) {
return (new Date().toISOString()) + ` ${msgTypeToStr[msgType]}: ${message}\n`; return this.formatDate(new Date()) + ` ${msgTypeToStr[msgType]}: ${message}\n`;
} }
log(msgType, message) { log(msgType, message) {
@@ -203,47 +212,18 @@ class Logger {
const mes = this.prepareMessage(msgType, message); const mes = this.prepareMessage(msgType, message);
if (!this.closed) {
for (let i = 0; i < this.handlers.length; i++) for (let i = 0; i < this.handlers.length; i++)
this.handlers[i].log(msgType, mes); this.handlers[i].log(msgType, mes);
} else {
console.log(mes);
}
} }
close() { async close() {
for (let i = 0; i < this.handlers.length; i++) for (let i = 0; i < this.handlers.length; i++)
this.handlers[i].close(); await this.handlers[i].close();
} this.closed = true;
cleanup(callback) {
// attach user callback to the process event emitter
// if no callback, it will still exit gracefully on Ctrl-C
callback = callback || (() => {});
process.on('cleanup', callback);
// do app specific cleaning before exiting
process.on('exit', () => {
this.close();
process.emit('cleanup');
});
// catch ctrl+c event and exit normally
process.on('SIGINT', () => {
this.log(LM_FATAL, 'Ctrl-C pressed, exiting...');
process.exit(2);
});
process.on('SIGTERM', () => {
this.log(LM_FATAL, 'Kill signal, exiting...');
process.exit(2);
});
//catch uncaught exceptions, trace, then exit normally
process.on('uncaughtException', e => {
try {
this.log(LM_FATAL, e.stack);
} catch (e) {
console.log(e.stack);
}
process.exit(99);
});
} }
} }

View File

@@ -7,6 +7,11 @@ const compression = require('compression');
const http = require('http'); const http = require('http');
const WebSocket = require ('ws'); const WebSocket = require ('ws');
const ayncExit = new (require('./core/AsyncExit'))();
ayncExit.init();
let log = null;
async function init() { async function init() {
//config //config
const configManager = new (require('./config'))();//singleton const configManager = new (require('./config'))();//singleton
@@ -18,7 +23,7 @@ async function init() {
//logger //logger
const appLogger = new (require('./core/AppLogger'))();//singleton const appLogger = new (require('./core/AppLogger'))();//singleton
await appLogger.init(config); await appLogger.init(config);
const log = appLogger.log; log = appLogger.log;
//dirs //dirs
log(`${config.name} v${config.version}, Node.js ${process.version}`); log(`${config.name} v${config.version}, Node.js ${process.version}`);
@@ -96,13 +101,15 @@ async function main() {
} }
} }
(async() => { (async() => {
try { try {
await init(); await init();
await main(); await main();
} catch (e) { } catch (e) {
if (log)
log(LM_FATAL, e);
else
console.error(e); console.error(e);
process.exit(1); ayncExit.exit(1);
} }
})(); })();