Files
liberama/server/core/Logger.js
2020-02-06 20:25:49 +07:00

250 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
Журналирование с буферизацией вывода
*/
const fs = require('fs-extra');
global.LM_OK = 0;
global.LM_INFO = 1;
global.LM_WARN = 2;
global.LM_ERR = 3;
global.LM_FATAL = 4;
global.LM_TOTAL = 5;
const LOG_CACHE_BUFFER_SIZE = 8192;
const LOG_BUFFER_FLUSH_INTERVAL = 200;
const LOG_ROTATE_FILE_LENGTH = 1000000;
const LOG_ROTATE_FILE_DEPTH = 9;
const LOG_ROTATE_FILE_CHECK_INTERVAL = 60000;
let msgTypeToStr = {
[LM_OK]: ' OK',
[LM_INFO]: ' INFO',
[LM_WARN]: ' WARN',
[LM_ERR]: 'ERROR',
[LM_FATAL]: 'FATAL ERROR',
[LM_TOTAL]: 'TOTAL'
};
class BaseLog {
constructor(params) {
this.params = params;
this.exclude = new Set(params.exclude);
this.outputBufferLength = 0;
this.outputBuffer = [];
this.flushing = false;
}
async flush() {
if (this.flushing || !this.outputBufferLength)
return;
this.flushing = true;
this.data = this.outputBuffer;
this.outputBufferLength = 0;
this.outputBuffer = [];
await this.flushImpl(this.data)
.catch(e => { console.log(e); process.exit(1); } );
this.flushing = false;
}
log(msgType, message) {
if (this.closed) { console.log(`Logger fatal error: log was closed (message to log: ${message}})`); process.exit(1); }
if (!this.exclude.has(msgType)) {
this.outputBuffer.push(message);
this.outputBufferLength += message.length;
if (this.outputBufferLength >= LOG_CACHE_BUFFER_SIZE && !this.flushing) {
this.flush();
}
if (!this.iid) {
this.iid = setInterval(() => {
if (!this.flushing) {
clearInterval(this.iid);
this.iid = 0;
this.flush();
}
}, LOG_BUFFER_FLUSH_INTERVAL);
}
}
}
close() {
if (this.closed)
return;
if (this.iid)
clearInterval(this.iid);
try {
if (this.flushing)
this.flushImplSync(this.data);
this.flushImplSync(this.outputBuffer);
} catch(e) {
console.log(e);
process.exit(1);
}
this.outputBufferLength = 0;
this.outputBuffer = [];
this.closed = true;
}
}
class FileLog extends BaseLog {
constructor(params) {
super(params);
this.fileName = params.fileName;
this.fd = fs.openSync(this.fileName, 'a');
this.rcid = 0;
}
close() {
if (this.closed)
return;
super.close();
if (this.fd)
fs.closeSync(this.fd);
if (this.rcid)
clearTimeout(this.rcid);
}
async rotateFile(fileName, i) {
let fn = fileName;
if (i > 0)
fn += `.${i}`;
let tn = fileName + '.' + (i + 1);
let exists = await fs.access(tn).then(() => true).catch(() => false);
if (exists) {
if (i >= LOG_ROTATE_FILE_DEPTH - 1) {
await fs.unlink(tn);
} else {
await this.rotateFile(fileName, i + 1);
}
}
await fs.rename(fn, tn);
}
async doFileRotationIfNeeded() {
this.rcid = 0;
let stat = await fs.fstat(this.fd);
if (stat.size > LOG_ROTATE_FILE_LENGTH) {
await fs.close(this.fd);
await this.rotateFile(this.fileName, 0);
this.fd = await fs.open(this.fileName, "a");
}
}
async flushImpl(data) {
if (this.closed)
return;
if (!this.rcid) {
await this.doFileRotationIfNeeded();
this.rcid = setTimeout(() => {
this.rcid = 0;
}, LOG_ROTATE_FILE_CHECK_INTERVAL);
}
await fs.write(this.fd, Buffer.from(data.join('')));
}
flushImplSync(data) {
fs.writeSync(this.fd, Buffer.from(data.join('')));
}
}
class ConsoleLog extends BaseLog {
async flushImpl(data) {
process.stdout.write(data.join(''));
}
flushImplSync(data) {
process.stdout.write(data.join(''));
}
}
//------------------------------------------------------------------
const factory = {
ConsoleLog,
FileLog,
};
class Logger {
constructor(params = null, cleanupCallback = null) {
this.handlers = [];
if (params) {
params.forEach((logParams) => {
let className = logParams.log;
let loggerClass = factory[className];
this.handlers.push(new loggerClass(logParams));
});
}
cleanupCallback = cleanupCallback || (() => {});
this.cleanup(cleanupCallback);
}
prepareMessage(msgType, message) {
return (new Date().toISOString()) + ` ${msgTypeToStr[msgType]}: ${message}\n`;
}
log(msgType, message) {
if (message == null) {
message = msgType;
msgType = LM_INFO;
}
const mes = this.prepareMessage(msgType, message);
for (let i = 0; i < this.handlers.length; i++)
this.handlers[i].log(msgType, mes);
}
close() {
for (let i = 0; i < this.handlers.length; i++)
this.handlers[i].close();
}
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);
});
}
}
module.exports = Logger;