Добавлен модуль AsyncExit для выполненния cleanup-процедур перед выходом из приложения
This commit is contained in:
@@ -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
111
server/core/AsyncExit.js
Normal 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;
|
||||||
@@ -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});
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.write(this.fd, Buffer.from(data.join('')));
|
if (this.fd)
|
||||||
|
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);
|
||||||
|
|
||||||
for (let i = 0; i < this.handlers.length; i++)
|
if (!this.closed) {
|
||||||
this.handlers[i].log(msgType, mes);
|
for (let i = 0; i < this.handlers.length; i++)
|
||||||
|
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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
console.error(e);
|
if (log)
|
||||||
process.exit(1);
|
log(LM_FATAL, e);
|
||||||
|
else
|
||||||
|
console.error(e);
|
||||||
|
ayncExit.exit(1);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
Reference in New Issue
Block a user