web-dev-server
Version:
Node.js simple http server for common development or training purposes.
698 lines • 29.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.Logger = void 0;
var tslib_1 = require("tslib");
var fs_1 = require("fs");
//import { serialize as V8Serialize } from "v8";
var path_1 = require("path");
var ObjectHelper_1 = require("./Helpers/ObjectHelper");
var StringHelper_1 = require("./Helpers/StringHelper");
var MapHelper_1 = require("./Helpers/MapHelper");
Error.prepareStackTrace = function (error, stacks) {
Object.defineProperty(error, 'stacks', {
configurable: false,
writable: false,
enumerable: true,
value: stacks
});
return error.stack;
};
var Logger = /** @class */ (function () {
/**
* @summary Create new Logger instance.
* @param logsDirFullPath Directory full path with log files.
* @param documentRoot Application or project document root to simplify logged source file paths.
*/
function Logger(logsDirFullPath, documentRoot) {
var _this = this;
this.streamWriting = false;
this.maxLogFileSize = 52428800; // 50 MB by default
this.allowedLevels = new Map();
this.logsStreams = new Map();
this.logsStreamsLengths = new Map();
this.logsCaches = new Map();
this.writeStackTrace = true;
this.writeStackTraceFuncArgs = false;
this.maxDepth = 3;
logsDirFullPath = path_1.resolve(logsDirFullPath).replace(/\\/g, '/');
documentRoot = path_1.resolve(documentRoot).replace(/\\/g, '/');
this.logsDirFullPath = StringHelper_1.StringHelper.TrimRight(logsDirFullPath, '/');
this.documentRoot = StringHelper_1.StringHelper.TrimRight(documentRoot, '/');
// allow all levels by default:
for (var levelName in Logger.LEVEL)
this.allowedLevels.set(Logger.LEVEL[levelName], true);
process.on('beforeExit', function (code) {
_this.logsStreams.forEach(function (stream, level) {
try {
stream.end();
stream.close();
}
catch (e) { }
});
});
}
/**
* @summary Create new Logger instance.
* @param logsDirFullPath Directory full path with log files.
* @param documentRoot Application or project document root to simplify logged source file paths.
*/
Logger.CreateNew = function (logsDirFullPath, documentRoot) {
return new Logger(logsDirFullPath, documentRoot);
};
/**
* @summary Get logger instance as singleton.
*/
Logger.GetInstance = function () {
return Logger.instance;
};
/**
* @summary Set logger instance as singleton.
* @param loggetInstance Logger instance.
*/
Logger.SetInstance = function (loggetInstance) {
return Logger.instance = loggetInstance;
};
/**
* @summary Set max. bytes for each log file. 50 MB by default.
* @see https://convertlive.com/u/convert/megabytes/to/bytes
* @param maxBytes Max bytes to create another log file (as number of bytes or as string like: 1K, 5M, 1G or 1T).
*/
Logger.prototype.SetMaxLogFileSize = function (maxBytes) {
if (maxBytes === void 0) { maxBytes = '50M'; }
if (!isNaN(maxBytes)) {
this.maxLogFileSize = Number(maxBytes);
}
else {
var maxBytesStr = String(maxBytes).toUpperCase();
var numberStr = maxBytesStr.replace(/[^0-9\.]/g, '');
var multiplier = maxBytesStr.replace(/[^KMGT]/g, '');
if (numberStr.length == 0)
throw new RangeError("Max. log file size is invalid.");
var numberFloat = parseFloat(numberStr);
if (multiplier.length > 0) {
multiplier = multiplier.substr(0, 1);
var multipliers = MapHelper_1.MapHelper.ObjectToMap({
K: 1024,
M: 1048576,
G: 1073741824,
T: 1099511627776
});
if (multipliers.has(multiplier) &&
numberFloat * multipliers.get(multiplier) < Number.MAX_SAFE_INTEGER)
numberFloat *= multipliers.get(multiplier);
}
this.maxLogFileSize = numberFloat;
}
return this;
};
/**
* @summary Enable or disable writing to logs by write streams. If disabled, there is used standard file append. Disabled by default.
* @param allowedLevels `true` to enable stream writing (for singleton logger) or `false` for multiple logger instances to the same files. `false` by default.
*/
Logger.prototype.SetStreamWriting = function (streamWriting) {
if (streamWriting === void 0) { streamWriting = true; }
if (!streamWriting && this.streamWriting)
this.logsStreams.forEach(function (stream, level) {
try {
stream.end();
stream.close();
}
catch (e) { }
});
this.streamWriting = streamWriting;
return this;
};
/**
* @summary Allowed levels to log. Rest of not presented levels are automatically disallowed.
* @param allowedLevels Allowed levels to log like: `[Logger.LEVEL.ERROR, Logger.LEVEL.DEBUG, 'customname', ...]`
*/
Logger.prototype.SetAllowedLevels = function (allowedLevels) {
var e_1, _a;
var _this = this;
// set all existing levels to false first:
this.allowedLevels.forEach(function (value, allowedLevelName) {
_this.allowedLevels.set(allowedLevelName, false);
});
try {
// allow only selected levels:
for (var allowedLevels_1 = tslib_1.__values(allowedLevels), allowedLevels_1_1 = allowedLevels_1.next(); !allowedLevels_1_1.done; allowedLevels_1_1 = allowedLevels_1.next()) {
var levelName = allowedLevels_1_1.value;
this.allowedLevels.set(levelName, true);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (allowedLevels_1_1 && !allowedLevels_1_1.done && (_a = allowedLevels_1.return)) _a.call(allowedLevels_1);
}
finally { if (e_1) throw e_1.error; }
}
return this;
};
/**
* @summary Set how to write stack trace.
* @param writeStackTrace If `true`, stack trace will be written into all log types, `false` otherwise, default `true`.
* @param writeStackTraceFuncArgs If `true`, stack trace will be written with called functions arguments into all log types, `false` otherwise, default `true`. Arguments serialization could be very large.
*/
Logger.prototype.SetStackTraceWriting = function (writeStackTrace, writeStackTraceFuncArgs) {
if (writeStackTrace === void 0) { writeStackTrace = true; }
if (writeStackTraceFuncArgs === void 0) { writeStackTraceFuncArgs = false; }
this.writeStackTrace = writeStackTrace;
if (writeStackTrace) {
this.writeStackTraceFuncArgs = writeStackTraceFuncArgs;
}
else {
this.writeStackTraceFuncArgs = false;
}
return this;
};
/**
* @summary Set max depth to dump objects.
* @param maxDepth Default is `3`.
*/
Logger.prototype.SetMaxDepth = function (maxDepth) {
if (maxDepth === void 0) { maxDepth = 3; }
this.maxDepth = maxDepth;
return this;
};
/**
* @summary Log any error.
* @param err Error instance to log or error message to generate an error internally and log the error instance.
* @param level Log level (log file name).
*/
Logger.prototype.Error = function (err, level) {
if (level === void 0) { level = 'error'; }
var date = new Date(), errMessage, errType, errStacks, stackTrace;
// check if current log level is allowed:
if (!this.allowedLevels.has(level) ||
!this.allowedLevels.get(level))
return this;
// if input is string, turn it into error:
if (err instanceof Error) {
errType = ObjectHelper_1.ObjectHelper.RealTypeOf(err);
errMessage = err.message;
if (this.writeStackTrace && err.stack.length > 0)
errStacks = err['stacks'];
}
else {
errType = 'Error';
errMessage = err.toString();
if (this.writeStackTrace) {
try {
throw new Error(err.toString());
}
catch (e1) {
errStacks = e1['stacks'];
}
}
}
// complete log record:
var logRecordStr = '[' + date.toJSON() + '] [' + errType + ']: ' + errMessage + "\n";
// serialize stack trace info if necessary:
if (this.writeStackTrace) {
stackTrace = this.getStackTraceItems(errStacks);
logRecordStr += this.serializeStackTrace(stackTrace) + "\n\n";
}
// write log record:
return this.appendToLogFile(logRecordStr, level);
};
/**
* @summary Log any stringified JS variable into log file with stack trace.
* @param obj any JS variable to log.
* @param level Log level (log file name).
*/
Logger.prototype.Log = function (obj, level) {
if (level === void 0) { level = 'debug'; }
var date = new Date(), objSerialized, errStacks = [], stackTrace;
// check if current log level is allowed:
if (!this.allowedLevels.has(level) ||
!this.allowedLevels.get(level))
return this;
// serialize given object:
objSerialized = this.serializeWhatIsPossible(obj, this.writeStackTrace, true);
// complete log record:
var logRecordStr = '[' + date.toJSON() + '] ' + objSerialized + "\n";
// serialize stack trace info if necessary:
if (this.writeStackTrace) {
// complete stack trace by dummy error:
try {
throw new Error('Place error.');
}
catch (e3) {
if (e3.stack.length > 0) {
errStacks = e3['stacks'];
errStacks = errStacks.slice(1);
}
}
stackTrace = this.getStackTraceItems(errStacks);
logRecordStr += this.serializeStackTrace(stackTrace) + "\n\n";
}
// write log record:
return this.appendToLogFile(logRecordStr, level);
};
Logger.prototype.appendToLogFile = function (msg, level) {
var _this = this;
var logFullPath = this.logsDirFullPath + '/' + level + Logger.LOGS_EXT;
if (this.streamWriting) {
if (this.logsStreamsLengths.has(level) && this.logsStreamsLengths.get(level) > this.maxLogFileSize) {
if (!this.logsStreams.has(level)) {
// still renaming:
this.logsCaches.set(level, this.logsCaches.get(level) + msg);
}
else {
// begin rename:
this.logsCaches.set(level, msg);
var stream = this.logsStreams.get(level);
stream.end();
stream.close();
this.logsStreams.delete(level);
this.renameFullLogFile(level, function () {
_this.logsStreamsLengths.set(level, 0);
var msgLocal = _this.logsCaches.get(level);
_this.logsCaches.set(level, '');
_this.appendToLogFileByStream(msgLocal, level, logFullPath);
});
}
}
else {
this.appendToLogFileByStream(msg, level, logFullPath);
}
}
else {
fs_1.exists(logFullPath, function (exists) {
if (!exists) {
_this.appendToLogFileByStandardWrite(msg, level, logFullPath);
}
else {
fs_1.stat(logFullPath, function (errLocal, stats) {
if (errLocal) {
_this.logsCaches.set(level, _this.logsCaches.get(level) + msg);
return console.error(errLocal);
}
if (stats.size > _this.maxLogFileSize) {
_this.renameFullLogFile(level, function () {
_this.logsStreamsLengths.set(level, 0);
var msgLocal = _this.logsCaches.get(level);
_this.logsCaches.set(level, '');
_this.appendToLogFileByStandardWrite(msgLocal, level, logFullPath);
});
}
else {
_this.appendToLogFileByStandardWrite(msg, level, logFullPath);
}
});
}
});
}
return this;
};
Logger.prototype.renameFullLogFile = function (level, cb) {
var _this = this;
var oldFullPath = this.logsDirFullPath + '/' + level + Logger.LOGS_EXT;
fs_1.stat(oldFullPath, function (errLocal1, stats) {
if (errLocal1)
return console.error(errLocal1);
var date = stats.ctime, newFileNameLevels = [
// _2020-01-01:
'_' + ([
date.getFullYear().toString(),
((date.getMonth() + 1) / 100).toFixed(2).substr(2),
(date.getDate() / 100).toFixed(2).substr(2)
].join('-')),
// _01-01
'_' + ([
(date.getHours() / 100).toFixed(2).substr(2),
(date.getMinutes() / 100).toFixed(2).substr(2),
(date.getSeconds() / 100).toFixed(2).substr(2)
].join('-')),
// _123
[
'_',
(date.getMilliseconds() / 1000).toFixed(3).substr(2)
].join('')
];
fs_1.readdir(_this.logsDirFullPath, function (errLocal2, files) {
if (errLocal2)
return console.error(errLocal2);
var newFileName = level;
for (var i = 0, l = newFileNameLevels.length; i < l; i++) {
newFileName += newFileNameLevels[i];
if (files.indexOf(newFileName + Logger.LOGS_EXT) === -1)
break;
}
fs_1.rename(_this.logsDirFullPath + '/' + level + Logger.LOGS_EXT, _this.logsDirFullPath + '/' + newFileName + Logger.LOGS_EXT, function (errLocal3) {
if (errLocal3)
return console.error(errLocal3);
cb();
});
});
});
};
Logger.prototype.appendToLogFileByStandardWrite = function (msg, level, logFullPath) {
fs_1.appendFile(logFullPath, msg, {
encoding: 'utf8',
mode: 438,
flags: 'a+'
}, function (errLocal) {
if (errLocal)
console.error(errLocal);
});
};
Logger.prototype.appendToLogFileByStream = function (msg, level, logFullPath) {
var _this = this;
var stream;
if (this.logsStreams.has(level)) {
stream = this.logsStreams.get(level);
}
else {
stream = fs_1.createWriteStream(logFullPath, {
flags: 'a+',
autoClose: false,
encoding: 'utf8',
mode: 438
});
this.logsStreams.set(level, stream);
this.logsStreamsLengths.set(level, 0);
}
stream.write(msg, function (errLocal) {
if (errLocal) {
stream.end();
stream.close();
_this.logsStreams.delete(level);
console.error(errLocal);
}
else {
_this.logsStreamsLengths.set(level, _this.logsStreamsLengths.get(level) + Buffer.byteLength(msg, 'utf8'));
}
});
};
Logger.prototype.serializeStackTrace = function (items) {
var result = [], item, fileLine;
for (var i = 0, l = items.length; i < l; i++) {
item = items[i];
fileLine = '';
if (!item.isNative && item.file && item.line && item.column)
fileLine = String(item.file) + ':' + String(item.line) + ':' + String(item.column);
result.push("\t-> " + item.fnFullName + '(' + item.argumentsSerialized + ');'
+ (fileLine.length > 0 ? "\n\t " + fileLine : ""));
}
return result.join("\n");
};
Logger.prototype.getStackTraceItems = function (stacks) {
var stackTraceItems = [];
for (var i = 0, l = stacks.length; i < l; i++)
stackTraceItems.push(this.getStackTraceItem(stacks[i]));
return stackTraceItems;
};
Logger.prototype.getStackTraceItem = function (stack) {
var isTopLevel, isConstructor, fnFullName, evalOrigin, fn, args = [], argsStr = '', file;
// arguments:
if (this.writeStackTraceFuncArgs) {
fn = stack.getFunction();
if (fn) {
args = [];
try {
args = fn.arguments ? [].slice.apply(fn.arguments) : [];
}
catch (e1) { }
if (args.length > 0) {
argsStr = this.getStackTraceItemSerializedArgs(args);
}
}
}
// file:
file = Boolean(stack.getScriptNameOrSourceURL)
? stack.getScriptNameOrSourceURL()
: stack.getFileName();
if (file) {
file = file.replace(/\\/g, '/');
if (file.indexOf(this.documentRoot) === 0)
file = '.' + file.substr(this.documentRoot.length);
}
// eval origin file:
evalOrigin = stack.getEvalOrigin();
if (evalOrigin)
evalOrigin = evalOrigin.replace(/\\/g, '/');
// function full name:
isTopLevel = stack.isToplevel();
isConstructor = stack.isConstructor();
fnFullName = this.getStackTraceItemFuncFullName(stack, isTopLevel, isConstructor);
// return result:
return {
stack: stack,
scope: stack.getThis(),
fnFullName: fnFullName,
isConstructor: isConstructor,
isNative: stack.isNative(),
isToplevel: isTopLevel,
isEval: stack.isEval(),
arguments: args,
argumentsSerialized: argsStr,
file: file,
line: stack.getLineNumber(),
column: stack.getColumnNumber(),
evalOrigin: evalOrigin
};
};
Logger.prototype.getStackTraceItemSerializedArgs = function (args) {
var arg, result = [], separator = '';
for (var j = 0, k = args.length; j < k; j++) {
arg = args[j];
result.push(separator);
result.push(this.serializeWhatIsPossible(arg, false, false));
separator = ',';
}
return result.join('');
};
Logger.prototype.serializeWhatIsPossible = function (obj, prettyPrint, addTypeName) {
if (prettyPrint === void 0) { prettyPrint = false; }
if (addTypeName === void 0) { addTypeName = true; }
var result;
try {
result = this.stringifyRecursive(prettyPrint, addTypeName, 0, '', obj);
}
catch (e) {
result = e.message;
}
return result;
};
Logger.prototype.stringifyRecursive = function (prettyPrint, addTypeName, level, indent, obj) {
var e_2, _a, e_3, _b, e_4, _c;
var result = [], baseSeparator = '', separator = '', rawValue, key, item, itemsIndent, newLine = "\n", doubleDot = ': ', isArray = false, isMap = false, isSet = false;
if (ObjectHelper_1.ObjectHelper.IsPrimitiveType(obj)) {
if (obj === undefined)
return 'undefined';
if (obj === null)
return 'null';
if (obj.constructor === Number) {
if (Number.isNaN(obj))
return 'NaN';
if (!Number.isFinite(obj)) {
if (obj < 0)
return '-Infinity';
return 'Infinity';
}
return JSON.stringify(obj);
}
else {
return JSON.stringify(obj);
}
}
else if (obj instanceof Function) {
return '[' + obj.name + ' Function(' + obj.length + ')]';
}
if (level == this.maxDepth)
return '[' + ObjectHelper_1.ObjectHelper.RealTypeOf(obj) + ']';
var objProto = Object.getPrototypeOf(obj);
if (prettyPrint) {
itemsIndent = indent + "\t";
baseSeparator = ",\n";
}
else {
newLine = '';
indent = '';
doubleDot = ':';
itemsIndent = '';
baseSeparator = ',';
}
if (
//Helpers.RealTypeOf(obj) == 'Array' | 'Uint8Array' | ... &&
'length' in objProto) {
isArray = true;
if (obj.length == 0) {
result.push('[]');
}
else {
result.push('[' + newLine);
for (var i = 0, l = obj.length; i < l; i++) {
try {
item = this.stringifyRecursive(prettyPrint, addTypeName, level + 1, itemsIndent, obj[i]);
}
catch (e1) {
item = '[' + ObjectHelper_1.ObjectHelper.RealTypeOf(obj[i]) + ']';
}
result.push(separator + itemsIndent + item);
separator = baseSeparator;
}
result.push(newLine + indent + ']');
}
}
else if (obj instanceof global.RegExp) {
var regExp = obj;
result.push('/' + regExp.source + '/' + regExp.flags);
}
else if (obj instanceof global.Map) {
isMap = true;
var objMap = obj;
if (objMap.size == 0) {
result.push('{}');
}
else {
result.push('{' + newLine);
try {
for (var objMap_1 = tslib_1.__values(objMap), objMap_1_1 = objMap_1.next(); !objMap_1_1.done; objMap_1_1 = objMap_1.next()) {
var _d = tslib_1.__read(objMap_1_1.value, 2), rawKey = _d[0], rawValue = _d[1];
try {
key = JSON.stringify(rawKey);
item = this.stringifyRecursive(prettyPrint, addTypeName, level + 1, itemsIndent, rawValue);
}
catch (e1) {
key = String(rawKey);
item = '[' + ObjectHelper_1.ObjectHelper.RealTypeOf(rawValue) + ']';
}
result.push(separator + itemsIndent + key + doubleDot + item);
separator = baseSeparator;
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (objMap_1_1 && !objMap_1_1.done && (_a = objMap_1.return)) _a.call(objMap_1);
}
finally { if (e_2) throw e_2.error; }
}
result.push(newLine + indent + '}');
}
}
else if (obj instanceof global.Set) {
isSet = true;
var objSet = obj;
if (objSet.size == 0) {
result.push('[]');
}
else {
result.push('[' + newLine);
try {
for (var objSet_1 = tslib_1.__values(objSet), objSet_1_1 = objSet_1.next(); !objSet_1_1.done; objSet_1_1 = objSet_1.next()) {
var rawValue = objSet_1_1.value;
try {
item = this.stringifyRecursive(prettyPrint, addTypeName, level + 1, itemsIndent, rawValue);
}
catch (e1) {
item = '[' + ObjectHelper_1.ObjectHelper.RealTypeOf(rawValue) + ']';
}
result.push(separator + itemsIndent + item);
separator = baseSeparator;
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (objSet_1_1 && !objSet_1_1.done && (_b = objSet_1.return)) _b.call(objSet_1);
}
finally { if (e_3) throw e_3.error; }
}
result.push(newLine + indent + ']');
}
}
else if (obj.exports && obj.exports.__esModule && obj.constructor && obj.constructor.name == 'Module') {
var file = String(obj.filename).replace(/\\/g, '/');
if (file.indexOf(this.documentRoot) === 0)
file = '.' + file.substr(this.documentRoot.length);
return '[' + file + ' Module]';
}
else {
var objKeys = Object.keys(obj);
if (objKeys.length == 0) {
result.push('{}');
}
else {
result.push('{' + newLine);
try {
for (var objKeys_1 = tslib_1.__values(objKeys), objKeys_1_1 = objKeys_1.next(); !objKeys_1_1.done; objKeys_1_1 = objKeys_1.next()) {
var rawKey2 = objKeys_1_1.value;
rawValue = obj[rawKey2];
try {
key = JSON.stringify(rawKey2);
item = this.stringifyRecursive(prettyPrint, addTypeName, level + 1, itemsIndent, rawValue);
}
catch (e1) {
key = String(rawKey2);
item = '[' + ObjectHelper_1.ObjectHelper.RealTypeOf(rawValue) + ']';
}
result.push(separator + itemsIndent + key + doubleDot + item);
separator = baseSeparator;
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (objKeys_1_1 && !objKeys_1_1.done && (_c = objKeys_1.return)) _c.call(objKeys_1);
}
finally { if (e_4) throw e_4.error; }
}
result.push(newLine + indent + '}');
}
}
if (addTypeName) {
result.push(' [' + ObjectHelper_1.ObjectHelper.RealTypeOf(obj));
if (isArray) {
result.push('(' + String(obj.length) + ')');
}
else if (isMap || isSet) {
result.push('(' + String(obj.size) + ')');
}
result.push(']');
}
return result.join('');
};
Logger.prototype.getStackTraceItemFuncFullName = function (stack, isTopLevel, isConstructor) {
var fnFullName, methodName, typeName, funcName;
if (isTopLevel) {
fnFullName = stack.getFunctionName();
}
else if (isConstructor) {
fnFullName = stack.getTypeName() + '.constructor';
}
else {
methodName = stack.getMethodName();
typeName = stack.getTypeName();
funcName = stack.getFunctionName();
if (methodName == null && typeName !== null) {
if (!funcName)
funcName = '<anonymous>';
fnFullName = typeName + '.' + stack.getFunctionName();
}
else if (methodName !== null && typeName !== null) {
fnFullName = typeName + '.' + methodName;
}
else {
fnFullName = stack.getFunctionName();
}
}
return fnFullName;
};
Logger.LEVEL = {
CRITICIAL: 'critical',
ERROR: 'error',
WARNING: 'warning',
NOTICE: 'notice',
INFO: 'info',
DEBUG: 'debug'
};
Logger.LOGS_EXT = '.log';
Logger.instance = null;
return Logger;
}());
exports.Logger = Logger;
;
//# sourceMappingURL=Logger.js.map