UNPKG

@the-hw-app/logger

Version:

A logger made for internal use by The Homework App.

373 lines (372 loc) 13.1 kB
"use strict"; 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;