UNPKG

web-dev-server

Version:

Node.js simple http server for common development or training purposes.

698 lines 29.6 kB
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