UNPKG

@apartmentlist/js-trace-logger

Version:
301 lines (300 loc) 9.74 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Logger = exports.formatUTCDateRuby = exports.extractParams = exports.compileTemplate = void 0; const stack_utils_1 = __importDefault(require("stack-utils")); const log_formatter_1 = require("./log_formatter"); const constant_1 = require("./constant"); const util_1 = require("./util"); const LoggerDefaultSeverity = 'info'; var util_2 = require("./util"); Object.defineProperty(exports, "compileTemplate", { enumerable: true, get: function () { return util_2.compileTemplate; } }); Object.defineProperty(exports, "extractParams", { enumerable: true, get: function () { return util_2.extractParams; } }); Object.defineProperty(exports, "formatUTCDateRuby", { enumerable: true, get: function () { return util_2.formatUTCDateRuby; } }); class Logger { /** * Followings are getter functions for private properties. I don't * really find a good reason to switch these values EXCEPT log * level in runtime, so I removed setter functions */ /** * function to generate a string out of Date object */ static get dateFunc() { return Logger._dateFunc; } /** * DD_ENV */ static get env() { return Logger._env; } /** * LOG_LEVEL */ static get level() { return Logger._level; } static set level(l) { const key = constant_1.LoggerSeverityRuntimeOption[l]; if (key) { Logger._level = key; Logger.severityIndex = constant_1.LoggerSeverityIndex[key]; } else { throw new TypeError(`invalid argument: Logger.level should be one of ${JSON.stringify(Object.values(constant_1.LoggerSeverityRuntimeOption))} but got "${l}"`); } } /** * template to generate a whole log line */ static get logTemplate() { return Logger._logTemplate; } /** * progname ~= DD_SERVICE; unless you set it specifically */ static get progname() { return Logger._progname; } /** * DD_SERVICE */ static get service() { return Logger._service; } /** * template to generate dd_trace string */ static get traceTemplate() { return Logger._traceTemplate; } /** * DD_VERSION */ static get version() { return Logger._version; } // PUBLIC methods // ============== static configure(option, tracer) { const { env, service, version, progname, logTemplate, traceTemplate, dateFunc } = option; Logger._env = env; Logger._service = service; Logger._version = version; Logger._progname = progname ? progname : service; if (logTemplate) { Logger._logTemplate = logTemplate; } if (traceTemplate) { Logger._traceTemplate = traceTemplate; } if (dateFunc) { Logger._dateFunc = dateFunc; } Logger.formatter = new log_formatter_1.LogFormatter({ env: Logger._env, service: Logger._service, version: Logger._version, progname: Logger._progname, logTemplate: Logger._logTemplate, traceTemplate: Logger._traceTemplate, dateFunc: Logger._dateFunc, }, tracer); } /** * console.log() compatible, but decorated with Trace ID and * Serverity of DEBUG. * * Any object is accepted as msg, and it will try to make JSON * string out of it. If the first agument is an instance of Error, * it will try to create error construct. * * @param ...msg - Any message */ static debug(...msg) { Logger.write('debug', msg); } /** * console.log() compatible, but decorated with Trace ID and * Serverity of INFO. * * Any object is accepted as msg, and it will try to make JSON * string out of it. If the first agument is an instance of Error, * it will try to create error construct. * * @param ...msg - Any message */ static info(...msg) { Logger.write('info', msg); } /** * console.log() compatible, but decorated with Trace ID and * Serverity of WARNING. * * Any object is accepted as msg, and it will try to make JSON * string out of it. If the first agument is an instance of Error, * it will try to create error construct. * * @param ...msg - Any message */ static warn(...msg) { Logger.write('warn', msg); } /** * console.log() compatible, but decorated with Trace ID and * Serverity of ERROR. * * Any object is accepted as msg, and it will try to make JSON * string out of it. If the first agument is an instance of Error, * it will try to create error construct. * * @param ...msg - Any message */ static error(...msg) { Logger.write('error', msg); } /** * Convert an error instance to JSON Error construct * * @param err - Capturing Error instance * @param extra - Object that you want to add to the JSON Error * construct */ static convertErrorToJson(err, extra) { const myStack = err.stack ? err.stack : ''; const result = { error: { class: err.constructor.name, message: err.message, stacktrace: Logger.stackUtil.clean(myStack).trim().split('\n'), }, }; if (extra) { Object.keys(extra).forEach((key) => { if (key === 'error') { return; } result[key] = extra[key]; }); } return result; } // PRIVATE methods // =============== static write(sev, msg) { const messageSevIndex = constant_1.LoggerSeverityIndex[sev]; if (Logger.severityIndex < messageSevIndex) { return; } if (Logger.passThru) { Logger.passThruWrite(sev, msg); return; } if (!Logger.formatter) { Logger.passThruWrite(sev, msg); return; } const dt = new Date(); const m = Logger.handleMessage(msg); Logger.concreteWrite(dt, sev, m); } static handleMessage(_msg) { let msg; if (_msg === null || _msg === undefined) { msg = _msg; } else if (_msg.length && _msg.length === 1) { msg = _msg[0]; } else { msg = _msg; } if (msg instanceof Error) { return JSON.stringify(Logger.convertErrorToJson(msg)); } if (Array.isArray(msg) && msg[0] instanceof Error) { let arg; if (msg.length === 2) { arg = msg[1]; } else { arg = msg.slice(1); } return JSON.stringify(Logger.convertErrorToJson(msg[0], arg)); } if (msg && typeof msg.toJSON === 'function') { return msg.toJSON(); } const msgType = typeof msg; // handle premitive types switch (msgType) { case 'bigint': case 'function': case 'string': return msg.toString(); break; case 'boolean': case 'number': // these still should be string because: // 1) TypeScript type suggests, // 2) also you want it to keep its type when // it goes as JSON-string, which it will be. return msg.toString(); break; case 'undefined': return 'undefined'; break; case 'symbol': return msg.description; break; } // only `object` should reach here try { const jsonStr = JSON.stringify(msg); return jsonStr; } catch (err) { // failed by JSON.stringify } try { const str = msg.toString(); return str; } catch (err) { // failed by toString…? how? } // Not sure how to reach here, but return '(could not be processed by Logger::handleMessage())'; } static concreteWrite(dt, sev, msg) { // if you ever need to write it to a FD, consider this: // https://www.npmjs.com/package/sonic-boom console.log(Logger.formatter.format(dt, sev, msg)); } static passThruWrite(sev, msg) { const args = [`[${sev}]`].concat(msg); console.log.call(console, ...args); } } exports.Logger = Logger; /** * It skips TraceID decoration if it's true (Default false) */ Logger.passThru = false; // See GETTER section for underscore private properties Logger._dateFunc = util_1.formatUTCDateRuby; Logger._env = 'development'; Logger._level = LoggerDefaultSeverity; Logger._logTemplate = '[${datetime}][${progname}][${severity}][${trace}] ${msg}'; Logger._progname = 'logger'; Logger._service = 'logger'; Logger._traceTemplate = 'dd.env=${env} dd.service=${service} dd.version=${version} dd.trace_id=${trace_id} dd.span_id=${span_id}'; Logger._version = 'unknown'; Logger.severityIndex = constant_1.LoggerSeverityIndex[LoggerDefaultSeverity]; Logger.stackUtil = new stack_utils_1.default({ cwd: process.cwd(), internals: stack_utils_1.default.nodeInternals(), });