/** * @fileOverview Utilities to simplify logging for the rest of the code. */ const winston = require('winston'); const _ = require('lodash'); const debug = require('debug')('logging:compliance'); /** * Requiring `winston-mongodb` will expose * `winston.transports.MongoDB` */ // eslint-disable-next-line import/no-unassigned-import require('winston-mongodb'); module.exports = logging; const MONGO_LOG_COLLECTION = 'ComplianceLog'; /** * Initialise log transports */ const transports = [ new winston.transports.Console({ 'name': 'console.info', 'format': winston.format.combine( winston.format.timestamp(), winston.format.simple() ), 'colorize': true, 'silent': false }) ]; let mongoTransport; /** * Create a very basic logger */ const logger = winston.createLogger({ 'level': 'info', transports }); /** * Trivially handle the 'error' event to prevent unhandled exception errors. */ logger.on('error', (error) => { debug('Logger error:', error); }); /** * Initialisation functions */ module.exports.init = { initMongoTransport }; /** * For test, also export a way to control the logger */ module.exports._test = { 'getLogger': () => logger, 'getTransports': () => transports }; /** * @typedef {function} BridgeLogFunction * @param {Object} req - Express request object (to access IP, request ID, etc.) * @param {string} [req.ip] - The IP address of the caller * @param {string} [req.bridgeUniqueId] - The unique id of this request * @param {string} [req.sessionData.User] - UserID for the user the request is for * @param {string} msg - The basic message to log * @param {Object} [opt] - Optional object containing additional values to log. These will be * automatically prefixed with '_' and added to the logged object. */ /** * @typedef {Object} BridgeLog * @property {BridgeLogFunction} info - log at info level * @property {BridgeLogFunction} error - log at error level */ /** * Factory function for initialising logging for the specified calling file. * * It returns an object containing log functions such as `log.info()`, `log.error()`. * The format of this functions is defined as @see BridgeLogFunction. * * @example * const log = require('/utils/logging.js')(__filename, 'utils:text:example'); * log.info(req, 'Some info text I want to log'); * log.error(req, 'Some error text I want to log', {customValue: 'Some custom value for this log'}); * * @param {string} file - The filename. Usually just __filename * @param {string} logID - A colon seperated id for this log group (e.g as used by debug()) * @returns {BridgeLog} - The logging object */ function logging(file, logID) { return { 'info': doLog.bind(undefined, file, logID, 'info'), 'error': doLog.bind(undefined, file, logID, 'error') }; } /** * Function to actually do the logging for any of the specific functions specified above. * * @param {string} file - the full filepath of the file that initialised this logger * @param {string} logId - id of the log group * @param {string} level - log level * @param {Object} req - Express request object (to access IP, requestID etc.) * @param {string} msg - The simple string to log * @param {Object} opt - Additional options to log */ function doLog(file, logId, level, req, msg, opt) { const toLog = { level, 'message': msg, 'meta': { logId, 'ip': req.ip, 'reqId': req.bridgeUniqueId, 'userId': _.get(req, 'session.data.user'), file } }; const loggableOpt = _.mapKeys(opt, (value, key) => `_${ key}`); // // Update the base object with the loggable options (prefix with _ per GELF); // _.assign(toLog.meta, loggableOpt); // // Call the real logger and return the result // return logger.log(toLog); } /** * The MongoDB `Db` class from the NodeJS driver * @typedef {Object} Db */ /** * Updates the logger to include a MongoDB transport that talks to the provided * `db` instance. * This also removes any prior MongoDB transport. * * @param {Db} db - the MongoDB `Db` instance to use for calling the DB. */ function initMongoTransport(db) { // Remove any existing mongodb transport if (mongoTransport) logger.remove(mongoTransport); // // Create a new mongodb transport and register it with the logger // mongoTransport = new winston.transports.MongoDB({ 'name': 'mongotransport', 'decolorize': true, 'storeHost': true, db, 'collection': MONGO_LOG_COLLECTION, 'format': winston.format.combine( winston.format.timestamp(), winston.format.printf((info) => info.message) ) }); logger.add(mongoTransport); }