/** * @fileOverview Helper utilities for initialising the morgan logging format */ 'use strict'; const morgan = require('morgan'); const debug = require('debug')('logging:activity'); const Writeable = require('stream').Writable; const mainDBP = require('../ComServe/mainDB-promises'); let initialised = false; const MAX_BUFFER = 1000; // Max entries to buffer if the db is down module.exports = { init, writeableStream }; /** * Initialises the morgan formats if it has not already been initialised */ function init() { if (initialised) { return; } // // Define a morgan token to get the userId from the session // morgan.token('user-id', (req) => { if (req.session && req.session.data) { return req.session.data.user; } else { return '-'; } }); // // Define an Apache Combined Log equivalent format that uses our user id // rather than default `basic-auth` user value. See: // https://github.com/expressjs/morgan#user-content-combined // for details of the base format. // morgan.format( 'bridge-combined', ':remote-addr - :user-id [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"' ); initialised = true; } /** * Function to create a new record for storing in the database. * * @param {string} record - the record value from Morgan * @returns {Object} - an object suitable for storing in MongoDB */ function entry(record) { return { timestamp: new Date(), request: record }; } /** * Returns a new Writeable stream which can be used to log Morgan entries to * the database via Morgan's `stream` parameter. * * @returns {Writeable} - A Writeable stream for use with Morgan logging */ function writeableStream() { let buffer = []; let writePending = false; const writeable = new Writeable({ objectMode: true, highWaterMark: 1, write: function write(record, encoding, next) { // Always log to stdout immediately process.stdout.write(record + '\n'); if (writePending || !mainDBP.mainDB.dbOnline) { // DB write in progress, or the DB is offline, so just buffer if (buffer.length < MAX_BUFFER) { buffer.push(entry(record)); debug('Buffered log message:', buffer.length, writePending); } else { process.stderr.write('Activity log buffer exceeded. MESSAGES WILL BE LOST!\n'); } } else { // Online so try to send entries to the db. // There may be buffered entries, so swap them into pending array // so more can buffer while we wait for the DB to confirm. const pending = buffer.slice(); buffer = []; // Add our new entry to the pending array pending.push(entry(record)); // Try to upload them to mongo debug('WRITE Started:', pending.length); writePending = true; mainDBP.addMany(mainDBP.mainDB.collectionActivityLog, pending, {}, false) .then((result) => { // The request ran, but may not have inserted everything. // If it didn't we can't really know which ones were and // were not inserted, so just notify the error. if (result.result.ok) { debug(' - WRITE OK:', result.result.n); } else { process.stderr.write('Some activity log entries may have failed to save to the db!\n'); debug(' - Write partial failure:', result.result.n); } writePending = false; return result; }) .catch((error) => { debug(' - WRITE ERROR received:', error); // The request didn't run for some reason; likely that the // database went down. Add them back into the buffer, // up to our max buffer size. // Note: keep the original items as they are likely to be // closer to the cause of the outage. // Add any new entries to the back of our pending list const temp = pending.concat(buffer); // And copy up to MAX_BUFFER items back over to the buffer buffer = temp.slice(0, MAX_BUFFER); writePending = false; return null; // We handled the error, so no need to pass on }); } // // Allow the server to continue without waiting for the result of the write to db // next(); } }); return writeable; }