@apartmentlist/js-trace-logger
Version:
Logger outputs messages with Trace ID
301 lines (300 loc) • 9.74 kB
JavaScript
;
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(),
});