UNPKG

@dyihoon90/glogging

Version:

HTTP request logging middleware & transaction function decorator for express, using winston

295 lines 14.3 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.__test__ = exports.GLogger = void 0; /* eslint-disable @typescript-eslint/no-explicit-any */ var is_secret_1 = __importDefault(require("is-secret")); var winston_1 = __importStar(require("winston")); var GLogger_interface_1 = require("./domainModels/GLogger.interface"); var core_1 = require("@js-joda/core"); var ObjUtils_1 = require("./utils/ObjUtils"); var lodash_1 = require("lodash"); var json_stringify_safe_1 = __importDefault(require("json-stringify-safe")); var serialize_error_1 = require("serialize-error"); var DEFAULT_CONFIG = { loggingMode: GLogger_interface_1.LoggingMode.PRODUCTION }; /** * How to use this class: * Initialize a new instance in your codebase. Use this instance as a singleton through the codebase. * if loggingMode is not provided, defaults to LoggingMode.PRODUCTION */ var GLogger = /** @class */ (function () { function GLogger(inputConfigs) { var _a; this.verboseMode = false; this.loggingMode = GLogger_interface_1.LoggingMode.PRODUCTION; this.loggingLevel = GLogger_interface_1.LoggingLevel.INFO; var configs = __assign(__assign({}, DEFAULT_CONFIG), inputConfigs); this.loggingMode = configs.loggingMode; switch (this.loggingMode) { case GLogger_interface_1.LoggingMode.LOCAL: this.loggingLevel = GLogger_interface_1.LoggingLevel.DEBUG; this.winstonLogger = winston_1.default.createLogger({ level: 'debug', format: winston_1.format.combine(formatTimestamp(), sensitiveDataRedacter) }); this.winstonLogger.add(new winston_1.transports.Console({ format: winston_1.format.combine(winston_1.format.printf(consoleMessageFormatterFactory(configs))) })); break; case GLogger_interface_1.LoggingMode.DEV: this.loggingLevel = GLogger_interface_1.LoggingLevel.INFO; this.winstonLogger = winston_1.default.createLogger({ level: 'info', format: winston_1.format.combine(formatTimestamp(), sensitiveDataRedacter) }); this.winstonLogger.add(new winston_1.transports.Console({ format: winston_1.format.combine(winston_1.format.printf(consoleMessageFormatterFactory(configs))) })); break; case GLogger_interface_1.LoggingMode.PRODUCTION: default: this.loggingLevel = GLogger_interface_1.LoggingLevel.INFO; this.winstonLogger = winston_1.default.createLogger({ level: 'info', format: winston_1.format.combine(formatTimestamp(), sensitiveDataRedacter) }); if ((_a = configs.overrideDefault) === null || _a === void 0 ? void 0 : _a.alwaysWriteToConsole) { this.winstonLogger.add(new winston_1.transports.Console({ format: winston_1.format.combine(winston_1.format.printf(consoleMessageFormatterFactory(configs))) })); } } } GLogger.prototype.toggleVerboseModeOn = function () { this.verboseMode = true; }; /** * Add a winston transport to this LogUtil instance * @param transport a winston-transport Log Transport instance */ GLogger.prototype.addLogTransport = function (transport) { this.winstonLogger.add(transport); return this; }; /** * Creates a log object of level debug * @example * info('msg', {mydata: "data"}) * // creates the following log object * {message: 'msg', level: 'debug', mydata: 'data'} * @param data any additional relevant data, as a javascript object. * If it contains a `message` property, the string is appended * If it contains a `level` property, that is ignored */ GLogger.prototype.debug = function (message, data) { if (this.verboseMode) { logVerbose('debug', message, data); } if (this.loggingLevel <= GLogger_interface_1.LoggingLevel.DEBUG) { this.winstonLogger.debug(message, data); } return this; }; /** * Creates a log object of level info * @example * info('msg', {mydata: "data"}) * // creates the following log object * {message: 'msg', level: 'info', mydata: 'data'} * @param data any additional relevant data, as a javascript object. * If it contains a `message` property, the string is appended * If it contains a `level` property, that is ignored */ GLogger.prototype.info = function (message, data) { if (this.verboseMode) { logVerbose('info', message, data); } if (this.loggingLevel <= GLogger_interface_1.LoggingLevel.INFO) { this.winstonLogger.info(message, data); } return this; }; /** * Creates a log object of level warn * @example * warn('msg', new Error('error msg'), {mydata: "data"}) * // creates the following log object * {message: 'msg', level: 'warn', mydata: 'data', additionalInfo: {error: {stack: 'errorstack!',message:'error msg',name:'Error'}}} * @param data any additional relevant data, as a javascript object. * If it contains a `message` property, the string is appended * If it contains a `level` property, that is ignored */ GLogger.prototype.warn = function (message, error, data) { if (this.verboseMode) { logVerbose('warn', message, data, error); } var dataToLog = data ? __assign({}, data) : {}; if (error) { dataToLog.additionalInfo = __assign(__assign({}, dataToLog.additionalInfo), { error: (0, serialize_error_1.serializeError)(error, { maxDepth: 5 }) }); } if (this.loggingLevel <= GLogger_interface_1.LoggingLevel.WARN) { this.winstonLogger.warn(message, dataToLog); } return this; }; /** * Creates a log object of level error * @example * error('msg', new Error('error msg'), {mydata: "data"}) * // creates the following log object * {message: 'msg', level: 'error', mydata: 'data', additionalInfo: {error: {stack: 'errorstack!',message:'error msg',name:'Error'}}} * @param data any additional relevant data, as a javascript object. * If it contains a `message` property, the string is appended * If it contains a `level` property, that is ignored */ GLogger.prototype.error = function (message, error, data) { if (this.verboseMode) { logVerbose('error', message, data, error); } var dataToLog = data ? __assign({}, data) : {}; if (error) { dataToLog.additionalInfo = __assign(__assign({}, dataToLog.additionalInfo), { error: (0, serialize_error_1.serializeError)(error) }); } if (this.loggingLevel <= GLogger_interface_1.LoggingLevel.ERROR) { this.winstonLogger.error(message, dataToLog); } return this; }; return GLogger; }()); exports.GLogger = GLogger; /** * Formatter for console logging in GLogging * Depending on whether trxCategory is passed in, either logs out basicLog or enrichedLog * @param info */ var consoleMessageFormatterFactory = function (config) { return function (info) { var _a, _b, _c; var sectionSeparator = ((_a = config.overrideDefault) === null || _a === void 0 ? void 0 : _a.consoleLogSectionSeparator) === '' || ((_b = config.overrideDefault) === null || _b === void 0 ? void 0 : _b.consoleLogSectionSeparator) ? (_c = config.overrideDefault) === null || _c === void 0 ? void 0 : _c.consoleLogSectionSeparator : '\n'; var _d = info, level = _d.level, message = _d.message, timestamp = _d.timestamp, additionalInfo = _d.additionalInfo, filename = _d.filename, data = __rest(_d, ["level", "message", "timestamp", "additionalInfo", "filename"]); var logString = "[".concat(timestamp, "][").concat(level.toUpperCase(), "]"); var trxCategory = data.trxCategory, trxId = data.trxId, trxModule = data.trxModule, trxName = data.trxName, trxStatus = data.trxStatus, timeTakenInMillis = data.timeTakenInMillis, userToken = data.userToken; if (!trxCategory) { var basicLog = logString .concat("[".concat(message, "][").concat(data ? formatWithLinebreakAndIndent(data, config) : 'no data', "]")) .concat(sectionSeparator) .concat("[".concat(additionalInfo ? formatWithLinebreakAndIndent(additionalInfo, config) : 'no additionalInfo', "]")) .concat(sectionSeparator); return basicLog; } var enrichedLog = logString .concat("[".concat(trxCategory, "][").concat(trxModule, "][").concat(trxId, "][").concat(trxName, "][").concat(trxStatus, "][").concat((timeTakenInMillis === null || timeTakenInMillis === void 0 ? void 0 : timeTakenInMillis.toString()) || 'time taken is not tracked', "ms]")) .concat("[".concat(message, "]")) .concat(sectionSeparator) .concat("[".concat(userToken ? formatWithLinebreakAndIndent(userToken, config) : 'no user token', "]")) .concat(sectionSeparator) .concat("[".concat(additionalInfo ? formatWithLinebreakAndIndent(additionalInfo, config) : 'no additionalInfo', "]")) .concat(sectionSeparator); return filename ? enrichedLog.concat("[".concat(filename, "]")) : enrichedLog; }; }; var formatTimestamp = winston_1.default.format(function (info) { info.timestamp = core_1.ZonedDateTime.now().format(core_1.DateTimeFormatter.ISO_OFFSET_DATE_TIME); return info; }); function formatWithLinebreakAndIndent(obj, config) { var _a, _b, _c, _d; try { var separator = ((_a = config === null || config === void 0 ? void 0 : config.overrideDefault) === null || _a === void 0 ? void 0 : _a.consoleLogSectionSeparator) === '' || ((_b = config === null || config === void 0 ? void 0 : config.overrideDefault) === null || _b === void 0 ? void 0 : _b.consoleLogSectionSeparator) ? (_c = config === null || config === void 0 ? void 0 : config.overrideDefault) === null || _c === void 0 ? void 0 : _c.consoleLogSectionSeparator : '\n'; return (_d = (0, json_stringify_safe_1.default)(obj, null, 1)) === null || _d === void 0 ? void 0 : _d.replace(/\\n/g, separator).replace(/\n/g, separator); } catch (e) { return 'Object cannot be stringified.'; } } function logVerbose(level, message, data, error) { console.log("[GLogger] ".concat(level, "() received message: ").concat(message)); if (data) { console.log("[GLogger] ".concat(level, "() received data: ").concat(formatWithLinebreakAndIndent(data))); } if (error) { console.log("[GLogger] ".concat(level, "() received error: ").concat(error.toString())); } } var sensitiveDataRedacter = winston_1.default.format(function (info) { var clonedInfo = (0, lodash_1.cloneDeep)(info); (0, ObjUtils_1.traverseAndMutateObject)(clonedInfo, redactSensitiveValue); return clonedInfo; })(); /** * Redacts all sensitive values * @param key property key of the object * @param value value of the object's property */ function redactSensitiveValue(key, value) { if (is_secret_1.default.key(key) || (typeof value === 'string' && is_secret_1.default.value(value))) { return '[REDACTED]'; } var nricRegex = /^([a-z]\d{7}[a-z])$/i; if (typeof value === 'string' && nricRegex.test(value)) { return '*****' + value.substring(5); } // for cases of array of nrics or uinfin, we redact the entire property var nricKeyRegex = /(nric|uinfin)/i; if (nricKeyRegex.test(key) && (0, lodash_1.isArray)(value)) { return '[REDACTED DUE TO NRIC OR UINFIN KEY]'; } return value; } // eslint-disable-next-line no-underscore-dangle exports.__test__ = { formatWithLinebreakAndIndent: formatWithLinebreakAndIndent }; //# sourceMappingURL=GLogger.js.map