ecs-logs-js
Version:
Simple Node.js console logger that outputs human friendly and ecs-logs compatible messages
190 lines • 6.86 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Logger = void 0;
const fast_safe_stringify_1 = __importDefault(require("fast-safe-stringify"));
const serialize_error_1 = require("serialize-error");
const extract_stack_1 = __importDefault(require("extract-stack"));
const replace_string_1 = __importDefault(require("replace-string"));
const chalk_1 = __importDefault(require("chalk"));
const js_yaml_1 = __importDefault(require("js-yaml"));
const LEVELS_MAP = {
emerg: 0,
alert: 1,
crit: 2,
error: 3,
warn: 4,
notice: 5,
info: 6,
debug: 7,
};
/** Checks that the level is valid */
function validateLevel(level) {
if (!(level in LEVELS_MAP)) {
throw new Error(`Invalid log level '${level}'`);
}
}
class ErrorArrayStack {
stack;
message;
name;
constructor(message, stack) {
this.message = message;
this.name = 'ErrorArrayStack';
this.stack = stack;
}
}
/** JSON.stringify replacer that converts unstringifyable values to stringifyable ones */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function jsonStringifyReplacer(_key, value) {
if (typeof value === 'bigint') {
return value.toString();
}
if (value instanceof Map) {
return Array.from(value.entries());
}
if (value instanceof Set) {
return Array.from(value);
}
if (value instanceof Error) {
const serializedError = (0, serialize_error_1.serializeError)(value);
// Tidy up the stack trace a bit and convert it to an array
const s = new ErrorArrayStack(serializedError.message || '', extract_stack_1.default.lines(value));
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key) && key !== 'message' && key !== 'stack' && key !== 'name') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
s[key] = value[key];
}
}
return s;
}
return value;
}
/** Creates a new logger instance. */
class Logger {
level = 'debug';
devMode = process.env.NODE_ENV === 'development';
constructor(options = {}) {
if (options.level) {
validateLevel(options.level);
this.level = options.level;
}
if (options.devMode) {
this.devMode = options.devMode;
}
}
/**
* Logs a message at the given log level.
* @param level Log level for the message.
* @param message The message to log.
* @param data Any additional data to log with the message. This can be any type.
*/
log = (level, message, data) => {
if (LEVELS_MAP[level] > LEVELS_MAP[this.level]) {
return;
}
const logLineObject = {
level: level.toUpperCase(),
time: new Date().toISOString(),
message,
data,
};
// Create JSON string with all the exotic values converted to JSON safe versions
let logLine = (0, fast_safe_stringify_1.default)(logLineObject, jsonStringifyReplacer);
// Format the logs in a human friendly way in development mode
if (this.devMode) {
// Construct the main log line and add some highlighting styles
// Just parse the production log because it already has all the data conversions applied
const log = JSON.parse(logLine);
logLine = chalk_1.default.bold(`\n${log.level}: ${log.message}`);
if (level === 'warn') {
logLine = chalk_1.default.yellow(logLine);
}
else if (LEVELS_MAP[level] <= LEVELS_MAP.error) {
logLine = chalk_1.default.red(logLine);
}
// Convert data to a compact and readable format
if (log.data) {
let data = js_yaml_1.default.safeDump(log.data, { schema: js_yaml_1.default.JSON_SCHEMA, lineWidth: Infinity });
// Indent the data slightly
data = data
.trim()
.split('\n')
.map((line) => ` ${line}`)
.join('\n');
// Shorten the absolute file paths
data = (0, replace_string_1.default)(data, process.cwd(), '.');
logLine += `\n${data}`;
}
}
process.stdout.write(logLine + '\n');
};
/**
* Logs a message at the EMERG log level.
* @param message The message to log.
* @param data Any additional data to log with the message. This can be any type.
*/
emerg = (message, data) => {
this.log('emerg', message, data);
};
/**
* Logs a message at the ALERT log level.
* @param message The message to log.
* @param data Any additional data to log with the message. This can be any type.
*/
alert = (message, data) => {
this.log('alert', message, data);
};
/**
* Logs a message at the CRIT log level.
* @param message The message to log.
* @param data Any additional data to log with the message. This can be any type.
*/
crit = (message, data) => {
this.log('crit', message, data);
};
/**
* Logs a message at the ERROR log level.
* @param message The message to log.
* @param data Any additional data to log with the message. This can be any type.
*/
error = (message, data) => {
this.log('error', message, data);
};
/**
* Logs a message at the WARN log level.
* @param message The message to log.
* @param data Any additional data to log with the message. This can be any type.
*/
warn = (message, data) => {
this.log('warn', message, data);
};
/**
* Logs a message at the NOTICE log level.
* @param message The message to log.
* @param data Any additional data to log with the message. This can be any type.
*/
notice = (message, data) => {
this.log('notice', message, data);
};
/**
* Logs a message at the INFO log level.
* @param message The message to log.
* @param data Any additional data to log with the message. This can be any type.
*/
info = (message, data) => {
this.log('info', message, data);
};
/**
* Logs a message at the DEBUG log level.
* @param message The message to log.
* @param data Any additional data to log with the message. This can be any type.
*/
debug = (message, data) => {
this.log('debug', message, data);
};
}
exports.Logger = Logger;
//# sourceMappingURL=index.js.map