@the-hw-app/logger
Version:
A logger made for internal use by The Homework App.
373 lines (372 loc) • 13.1 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LogLevel = exports.Logger = void 0;
const lodash_1 = require("lodash");
/**
* @name Environment
* @description Stores the Environment types that can be used in the application.
* @enum {string}
* @readonly
*/
var Environment;
(function (Environment) {
Environment["testing"] = "testing";
Environment["development"] = "development";
Environment["production"] = "production";
Environment["staging"] = "staging";
})(Environment || (Environment = {}));
/**
* @name LogLevel
* @description Stores the log levels that can be used in the application.
* @enum {string}
* @readonly
*/
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["emergency"] = 0] = "emergency";
LogLevel[LogLevel["alert"] = 1] = "alert";
LogLevel[LogLevel["critical"] = 2] = "critical";
LogLevel[LogLevel["error"] = 3] = "error";
LogLevel[LogLevel["warning"] = 4] = "warning";
LogLevel[LogLevel["notice"] = 5] = "notice";
LogLevel[LogLevel["info"] = 6] = "info";
LogLevel[LogLevel["debug"] = 7] = "debug";
})(LogLevel || (LogLevel = {}));
exports.LogLevel = LogLevel;
/**
* @class Logger
* @description Serves as a wrapper for the graylog2 library and console logging.
* @author [KryptoBlack] <https://github.com/KryptoBlack>
* @version 1.0.0
* @license MIT
* @example
* import { Logger } from 'logger';
* let logger: Logger = new Logger("xyz-service");
* logger.info('500 Server Failed', { status: 500 });
* logger.close();
* @todo Add a config to allow debug level logging to use graylog at production/staging environment.
* @todo Handle Invalid environment in a better way.
*/
class Logger {
/**
* @name constructor
* @description This is the constructor for the Logger class.
* @param hostname {string}
* @param env {number}
* @memberof Logger
*/
constructor(o) {
this.NODE_ENV = o.env ? o.env : Environment.development;
if (!(this.NODE_ENV in Environment)) {
const _env = Object.keys(Environment)
.filter((item) => isNaN(Number(item)))
.join(', ');
this._log(LogLevel.error, `Invalid environment "${process.env.NODE_ENV}" for logger. Should be one of ${_env}.`);
process.exit(1);
}
/**
* GELF Config.
*/
if (this.NODE_ENV == Environment.production || this.NODE_ENV == Environment.staging) {
this.gelfLogger = require('gelf-pro');
this.gelfLogger.setConfig({
fields: { facility: 'Node.js', owner: o.hostname },
filter: [
(message) => {
// rejects a "debug" message
return message.level < 7;
},
],
transform: [
(message) => {
// unwind an error
if ((0, lodash_1.isError)(message.error)) {
message.error = { message: message.error.message, stack: message.error.stack };
}
return message;
},
],
broadcast: [
(message) => {
// broadcasting to console
this.console(message.level)(message.short_message);
},
],
levels: {},
aliases: {},
adapterName: 'tcp',
adapterOptions: {
// this object is passed to the adapter.connect() method
// common
host: o.host,
port: 12201,
// tcp adapter example
family: 4, // tcp only; optional; version of IP stack; default: 4
},
});
}
this._log(LogLevel.info, `Logger initialized on ${this.NODE_ENV} Environment`);
}
/**
* @name log
* @description This function is used to log messages by passing the level.
* @param level
* @param message
* @param meta
* @returns void
* @memberof Logger
* @example
* @todo better way to handle keyword args.
* import { Logger } from 'logger';
* let logger: Logger = new Logger("xyz-service");
* logger.log(LogLevel.info, '500 Server Failed', { status: 500 });
* logger.close();
*/
log(_a) {
var { level, message } = _a, meta = __rest(_a, ["level", "message"]);
this._log(LogLevel[level], message, meta ? meta : undefined);
}
/**
* @name serve
* @description Serves middleware service for the logger.
* @param {MiddlewareArgs} o
* @returns {any}
* @memberof Logger
* @example
* @todo add a strict type for return value.
* @todo handle validation of key level in a better way.
* import { Logger } from 'logger';
*
* ...
*
* let logger: Logger = new Logger("xyz-service");
* const options = {
* definition: {
* openapi: "3.0.0",
* info: { ... },
* ...
* };
* const swaggerSpecs: object = swaggerJsdoc(options);
* app.use(logger.serve({ swaggerSpecs: swaggerSpecs }));
*
* ...
*
*/
serve(o) {
// Validate specs
if (!o.swaggerSpecs) {
this._log(LogLevel.critical, 'Swagger specs not provided');
process.exit(1);
}
// Validate specs path
if (!o.swaggerSpecs.paths) {
this._log(LogLevel.critical, 'Swagger specs paths not provided');
process.exit(1);
}
const levelKey = 'level';
// keys would be paths like: /ping, /users, /users/{id}
Object.keys(o.swaggerSpecs.paths).forEach((path) => {
// keys would be like: "get", "post", "put", "delete", "patch"
const obj = o.swaggerSpecs.paths[path];
Object.keys(obj).forEach((method) => {
const response = obj[method].responses;
Object.keys(response).forEach((status) => {
// Checks if the key exists and if it is one of the allowed levels
if (!(levelKey in response[status] && response[status][levelKey] in LogLevel)) {
this._log(LogLevel.critical, `Log level not provided for path ${path} and method ${method}`);
process.exit(1);
}
});
});
});
return (req, res, next) => {
var _a, _b, _c, _d, _e, _f;
// Saves the response body in a variable
const send = res.send;
res.send = (responseBody) => {
res.locals.responseBody = responseBody;
res.send = send;
return res.send(responseBody);
};
const startTime = Date.now();
next();
const latency = Date.now() - startTime;
const level = (_e = (_d = (_c = (_b = (_a = o.swaggerSpecs.paths) === null || _a === void 0 ? void 0 : _a[req.url]) === null || _b === void 0 ? void 0 : _b[req.method.toLowerCase()].responses) === null || _c === void 0 ? void 0 : _c[res.statusCode]) === null || _d === void 0 ? void 0 : _d.level) !== null && _e !== void 0 ? _e : 'info';
this._log(LogLevel[level], `${req.method} ${req.url} ${res.statusCode}`, {
staus: res.statusCode,
url: req.url,
method: req.method,
queryLatency: (_f = `${res.locals.queryLatency} ms`) !== null && _f !== void 0 ? _f : `Not available`,
latency: `${latency} ms`,
request: JSON.stringify({
method: req.method,
url: req.url,
cookies: req.cookies,
headers: req.headers,
body: Object.keys(req.body).length > 0 ? req.body : undefined,
}),
response: JSON.stringify({
status: res.statusCode,
statusMessage: res.statusMessage,
headers: res.getHeaders(),
body: res.locals.responseBody,
}),
authenticated: res.locals.authenticated ? true : false,
user: res.locals.authenticated ? JSON.stringify(res.locals.user) : undefined,
});
};
}
/**
*
* @name console
* @description This function is used to return console logger by level.
* @memberof Logger
* @param level
*/
console(level) {
if (level < 4)
return console.error;
else if (level < 6)
return console.warn;
else if (level == 7)
return console.info;
else
return console.debug;
}
/**
* @param level {logLevel}
* @param message {string}
* @param meta {any}
* @description This function is used to decide what to call the graylog function or console function.
* @private
* @returns void
* @memberof Logger
*/
_log(level, message, meta) {
return __awaiter(this, void 0, void 0, function* () {
if (this.NODE_ENV == Environment.production || this.NODE_ENV == Environment.staging) {
try {
new Promise((resolve) => {
this.gelfLogger[LogLevel[level]](message, meta, () => {
resolve({
level: LogLevel[level],
message: message,
meta: meta,
});
});
});
}
catch (error) {
console.error(error);
}
}
else {
if (meta == undefined || Object.keys(meta).length == 0)
this.console(level)(message);
else
this.console(level)(message, meta);
}
});
}
/**
* @name info
* @param message
* @param meta
* @returns void
* @memberof Logger
*/
info(message, ...meta) {
this._log(LogLevel.info, message, ...meta);
}
/**
* @name error
* @param message
* @param meta
* @returns void
* @memberof Logger
*/
error(message, ...meta) {
this._log(LogLevel.error, message, ...meta);
}
/**
* @name emergency
* @param message
* @param meta
* @returns void
* @memberof Logger
*/
emergency(message, ...meta) {
this._log(LogLevel.emergency, message, ...meta);
}
/**
* @name alert
* @param message
* @param meta
* @returns void
* @memberof Logger
*/
alert(message, ...meta) {
this._log(LogLevel.alert, message, ...meta);
}
/**
* @name critical
* @param message
* @param meta
* @returns void
* @memberof Logger
*/
critical(message, ...meta) {
this._log(LogLevel.critical, message, ...meta);
}
/**
* @name warning
* @param message
* @param meta
* @returns void
* @memberof Logger
*/
warning(message, ...meta) {
this._log(LogLevel.warning, message, ...meta);
}
/**
* @name notice
* @param message
* @param meta
* @returns void
* @memberof Logger
*/
notice(message, ...meta) {
this._log(LogLevel.notice, message, ...meta);
}
/**
* @name debug
* @param message
* @param meta
* @returns void
* @memberof Logger
*/
debug(message, ...meta) {
this._log(LogLevel.debug, message, ...meta);
}
}
exports.Logger = Logger;