UNPKG

mqlight-dev

Version:
668 lines (596 loc) 21.6 kB
/* %Z% %W% %I% %E% %U% */ /* * <copyright * notice="lm-source-program" * pids="5725-P60" * years="2014,2016" * crc="3568777996" > * Licensed Materials - Property of IBM * * 5725-P60 * * (C) Copyright IBM Corp. 2014, 2016 * * US Government Users Restricted Rights - Use, duplication or * disclosure restricted by GSA ADP Schedule Contract with * IBM Corp. * </copyright> */ /* jslint node: true */ /* jshint -W083,-W097 */ 'use strict'; var logger = exports; var pkg = require('./package.json'); var os = require('os'); var moment = require('moment'); var npmlog = require('npmlog'); var fs = require('fs'); var util = require('util'); var isWin = (os.platform() === 'win32'); var stack = ['<stack unwind error>']; var startLevel; var historyLevel; var ffdcSequence = 0; var fd = 0; var dataSize; var exceptionThrown = null; var potentialUnwinds = 0; var ENTRY_IND = '>-----------------------------------------------------------'; var EXIT_IND = '<-----------------------------------------------------------'; var HEADER_BANNER = '+---------------------------------------' + '---------------------------------------+'; var styles = { blue: { fg: 'blue', bg: 'black' }, green: { fg: 'green', bg: 'black' }, inverse: { inverse: true }, red: { fg: 'red', bg: 'black' }, yellow: { fg: 'yellow', bg: 'black' } }; /* * Set the npmlog heading to include a timestamp and process identifier. */ Object.defineProperty(npmlog, 'heading', { get: function() { return moment().format('HH:mm:ss.SSS') + ' [' + process.pid + ']'; } }); /* * Write a log or ffdc header, including basic information about the program * and host. */ var header = function(lvl, clientId, options) { if (npmlog.levels[npmlog.level] <= npmlog.levels[lvl]) { npmlog.log(lvl, clientId, HEADER_BANNER); npmlog.log(lvl, clientId, '| IBM MQ Light Node.js Client Module -', options.title); npmlog.log(lvl, clientId, HEADER_BANNER); npmlog.log(lvl, clientId, '| Date/Time :-', moment().format('ddd MMMM DD YYYY HH:mm:ss.SSS Z')); npmlog.log(lvl, clientId, '| Host Name :-', os.hostname()); npmlog.log(lvl, clientId, '| Operating System :-', os.type(), os.release()); npmlog.log(lvl, clientId, '| Architecture :-', os.platform(), os.arch()); npmlog.log(lvl, clientId, '| Node Version :-', process.version); npmlog.log(lvl, clientId, '| Node Path :-', process.execPath); npmlog.log(lvl, clientId, '| Node Arguments :-', process.execArgs); if (!isWin) { npmlog.log(lvl, clientId, '| User Id :-', process.getuid()); npmlog.log(lvl, clientId, '| Group Id :-', process.getgid()); } npmlog.log(lvl, clientId, '| Name :-', pkg.name); npmlog.log(lvl, clientId, '| Version :-', pkg.version); npmlog.log(lvl, clientId, '| Description :-', pkg.description); npmlog.log(lvl, clientId, '| Installation Path :-', __dirname); npmlog.log(lvl, clientId, '| Uptime :-', process.uptime()); npmlog.log(lvl, clientId, '| Log Level :-', npmlog.level); npmlog.log(lvl, clientId, '| Data Size :-', dataSize); if ('fnc' in options) { npmlog.log(lvl, clientId, '| Function :-', options.fnc); } if ('probeId' in options) { npmlog.log(lvl, clientId, '| Probe Id :-', options.probeId); } if ('ffdcSequence' in options) { npmlog.log(lvl, clientId, '| FFDCSequenceNumber:-', options.ffdcSequence++); } if (potentialUnwinds !== 0) { npmlog.log(lvl, clientId, '| potentialUnwinds :-', potentialUnwinds); } npmlog.log(lvl, clientId, HEADER_BANNER); if ('fnc' in options && options.fnc.indexOf('SIG') == 0) { npmlog.log(lvl, clientId, '(Set MQLIGHT_NODE_NO_HANDLER to ' + 'disable user requested FFDCs)'); } npmlog.log(lvl, clientId, ''); } }; /** * Set the logging level. * * @param {String} level The logging level to write at. */ logger.setLevel = function(level) { var lvl = String(level).toLowerCase().trim(); if (npmlog.levels[lvl]) { npmlog.level = lvl; header('header', logger.NO_CLIENT_ID, {title: 'Log'}); var debug = process.env.PN_TRACE_FRM + ',' || ''; if (npmlog.levels[npmlog.level] <= npmlog.levels.debug) { logger.log('debug', logger.NO_CLIENT_ID, 'Setting basic amqp10 debug'); process.env.DEBUG = debug + 'amqp10:*,-amqp10:session,-amqp10:framing,-amqp10:sasl'; if (npmlog.levels[npmlog.level] <= npmlog.levels.detail) { logger.log('debug', logger.NO_CLIENT_ID, 'Setting detailed amqp10 debug'); process.env.DEBUG = debug + 'amqp10:*'; } } else if (process.env.DEBUG && /amqp10:/.test(debug)) { logger.log('debug', logger.NO_CLIENT_ID, 'Unsetting amqp10 debug'); debug = debug.slice(0, debug.indexOf('amqp10')); if (debug.length > 0) { process.env.DEBUG = debug; } else { delete process.env.DEBUG; } } } else { console.error('ERROR: MQ Light log level \'' + lvl + '\' is invalid'); } }; /** * Get the logging level. * * @return {String} The logging level. */ logger.getLevel = function() { return npmlog.level; }; /** * Set the logging stream. * * @param {String} stream The stream to log to. If the value * isn't 'stderr' or 'stdout' then stream will be treated * as a file which will be written to as well as * stderr/stdout. */ logger.setStream = function(stream) { if (stream === 'stderr') { // Log to stderr. npmlog.stream = process.stderr; // Stop writing to a file. if (fd) { fs.closeSync(fd); } } else if (stream === 'stdout') { // Log to stdout. npmlog.stream = process.stdout; // Stop writing to a file. if (fd) { fs.closeSync(fd); } } else { // A file has been specified. As well as writing to stderr/stdout, we // additionally write the output to a file. // Stop writing to an existing file. if (fd) { fs.closeSync(fd); } // Open the specified file. fd = fs.openSync(stream, 'a', '0644'); // Set up a listener for log events. npmlog.on('log', function(m) { if (fd) { if (npmlog.levels[npmlog.level] <= npmlog.levels[m.level]) { // We'll get called for every log event, so filter to ones we're // interested in. fs.writeSync(fd, util.format('%s %s %s %s\n', npmlog.heading, npmlog.disp[m.level], m.prefix, m.message)); } } }); } }; /** * Set the amount of message data that will get logged. * * @param {Number} size amount of message data that will get logged. */ logger.setDataSize = function(size) { logger.entry('logger.setDataSize', logger.NO_CLIENT_ID); logger.log('parms', logger.NO_CLIENT_ID, 'size:', size); if (typeof size === 'string') { dataSize = parseInt(size); if (isNaN(dataSize)) { throw new TypeError('MQLIGHT_NODE_MESSAGE_DATA_SIZE is not a number'); } } else { dataSize = size; } logger.exit('logger.setDataSize', logger.NO_CLIENT_ID); }; /** * Get the amount of message data that will get logged. * * @return {Number} The data size. */ logger.getDataSize = function() { return dataSize; }; /** * Log entry into a function, specifying the logging level to * write at. * * @param {String} lvl The logging level to write at. * @param {String} name The name of the function being entered. * @param {String} id The id of the client causing the function * to be entered. */ logger.entryLevel = function(lvl, name, id) { if (exceptionThrown) { logger.log('error', id, '* Uncaught exception'); exceptionThrown = null; while (stack.length > 1) { stack.pop(); } } npmlog.log(lvl, id, ENTRY_IND.substring(0, stack.length), name); stack.push(name); }; /** * Log entry into a function. * * @param {String} name The name of the function being entered. * @param {String} id The id of the client causing the function * to be entered. */ logger.entry = function(name, id) { logger.entryLevel('entry', name, id); }; /** * Log exit from a function, specifying the logging level to * write at. * * @param {String} lvl The logging level to write at. * @param {String} name The name of the function being exited. * @param {String} id The id of the client causing the function * to be exited. * @param {Object} rc The function return code. */ logger.exitLevel = function(lvl, name, id, rc) { // Only log object type returns if object logging is enabled. if (npmlog.levels[npmlog.level] <= npmlog.levels.object) { npmlog.log(lvl, id, EXIT_IND.substring(0, Math.max(1, stack.length - 1)), name, rc ? rc : ''); } else { npmlog.log(lvl, id, EXIT_IND.substring(0, Math.max(1, stack.length - 1)), name, rc ? (typeof rc === 'object' ? '[object]' : rc) : ''); } var last; do { // Check if we've unwound to the bottom of the stack. if (stack.length == 1) { // We have. Generate an FFDC if we believe the stack to be good. Most // likely we've exited with the wrong function name. if (potentialUnwinds === 0) { logger.ffdc('logger.exitLevel', 10, null, name); } potentialUnwinds--; logger.log('debug', id, 'Potential unwinds decreased to', potentialUnwinds); break; } // Get rid of the last function put on the stack. last = stack.pop(); } while (last != name); }; /** * Log exit from a function. * * @param {String} name The name of the function being exited. * @param {String} id The id of the client causing the function * to be exited. * @param {Object} rc The function return code. */ logger.exit = function(name, id, rc) { logger.exitLevel('exit', name, id, rc); }; /** * Log data. * * @this {log} * @param {String} lvl The level at which to log the data. * @param {String} id The id of the client logging the data. * @param {Object} args The data to be logged. */ logger.log = function(lvl, id, args) { if (npmlog.levels[npmlog.level] <= npmlog.levels[lvl]) { npmlog.log.apply(this, arguments); } }; /** * Log message body. * * @this {log} * @param {String} id The id of the client logging the data. * @param {Object} data The message body to be logged subject to * specified data size. Must be either a string or a * Buffer object. */ logger.body = function(id, data) { if (npmlog.levels[npmlog.level] <= npmlog.levels.data) { npmlog.log('data', id, '! length:', data.length); if (typeof data === 'string') { if ((dataSize >= data.length) || (dataSize < 0)) { npmlog.log('data', id, '! string:', data); } else { npmlog.log('data', id, '! string:', data.substring(0, dataSize), '...'); } } else { if ((dataSize >= data.length) || (dataSize < 0)) { npmlog.log('data', id, '! buffer:', data.toString('hex')); } else { npmlog.log('data', id, '! buffer:', data.toString('hex', 0, dataSize), '...'); } } } }; /** * Log an exception being thrown, specifying the logging level to * write at. * * @this {log} * @param {String} lvl The logging level to write at. * @param {String} name The name of the function throwing the * exception. * @param {String} id The id of the client logging the * exception. * @param {Object} err The exception being thrown. */ logger.throwLevel = function(lvl, name, id, err) { logger.log('error', id, '* Thrown exception:', err); exceptionThrown = err; logger.exitLevel(lvl, name, id, 'Exception thrown'); potentialUnwinds += stack.length - 1; logger.log('debug', id, 'Potential unwinds increased to', potentialUnwinds); }; /** * Log an exception being thrown. * * @this {log} * @param {String} name The name of the function throwing the * exception. * @param {String} id The id of the client logging the * exception. * @param {Error} err The exception being thrown. */ logger.throw = function(name, id, err) { logger.throwLevel('exit', name, id, err); }; /** * Log an exception being caught, specifying the logging level to * write at. * * @this {log} * @param {String} lvl The logging level to write at. * @param {String} name The name of the function catching the * exception. * @param {String} id The id of the client logging the data. * @param {Error} err The exception being caught. */ logger.caughtLevel = function(lvl, name, id, err) { logger.log('error', id, '* Caught exception:', err); if (exceptionThrown) { exceptionThrown = null; while (stack.length > 1) { if (stack[stack.length - 1] === name) { break; } stack.pop(); potentialUnwinds--; logger.log('debug', id, 'Potential unwinds decreased to', potentialUnwinds); } if (stack.length == 1) { logger.entryLevel(lvl, name, id); } } }; /** * Log an exception being caught. * * @this {log} * @param {String} name The name of the function catching the * exception. * @param {String} id The id of the client logging the data. * @param {Error} err The exception being caught. */ logger.caught = function(name, id, err) { logger.caughtLevel('entry', name, id, err); }; /** * Dump First Failure Data Capture information in the event of * failure to aid in diagnosis of an error. * * @param {String=} opt_fnc The name of the calling function. * @param {Number=} opt_probeId An identifier for the error * location. * @param {Client=} opt_client The client having a problem. * @param {Object=} opt_data Extra data to aid in problem * diagnosis. */ logger.ffdc = function(opt_fnc, opt_probeId, opt_client, opt_data) { var opts = { title: 'First Failure Data Capture', fnc: opt_fnc || 'User-requested FFDC by function', probeId: opt_probeId || 255, ffdcSequence: ffdcSequence++, clientId: opt_client ? opt_client.id : logger.NO_CLIENT_ID }; logger.entry('logger.ffdc', opts.clientId); logger.log('parms', opts.clientId, 'fnc:', opt_fnc); logger.log('parms', opts.clientId, 'probeId:', opt_probeId); logger.log('parms', opts.clientId, 'data:', opt_data); if (npmlog.levels[npmlog.level] <= npmlog.levels.ffdc) { header('ffdc', opts.clientId, opts); npmlog.log('ffdc', opts.clientId, new Error().stack); npmlog.log('ffdc', opts.clientId, ''); npmlog.log('ffdc', opts.clientId, 'Function Stack'); npmlog.log('ffdc', opts.clientId, stack.slice(1)); npmlog.log('ffdc', opts.clientId, ''); npmlog.log('ffdc', opts.clientId, 'Function History'); for (var idx = 0; idx < npmlog.record.length; idx++) { var rec = npmlog.record[idx]; if ((rec.level !== 'ffdc') && (npmlog.levels[rec.level] >= npmlog.levels[historyLevel])) { npmlog.log('ffdc', opts.clientId, '%d %s %s %s', rec.id, npmlog.disp[rec.level], rec.prefix, rec.message); } } if (opt_client) { npmlog.log('ffdc', opts.clientId, ''); npmlog.log('ffdc', opts.clientId, 'Client'); npmlog.log('ffdc', opts.clientId, opt_client); } if (opt_data) { npmlog.log('ffdc', opts.clientId, ''); npmlog.log('ffdc', opts.clientId, 'Data'); npmlog.log('ffdc', opts.clientId, opt_data); } npmlog.log('ffdc', opts.clientId, ''); npmlog.log('ffdc', opts.clientId, 'Memory Usage'); npmlog.log('ffdc', opts.clientId, process.memoryUsage()); if ((ffdcSequence === 1) || (opts.probeId === 255)) { npmlog.log('ffdc', opts.clientId, ''); npmlog.log('ffdc', opts.clientId, 'Environment Variables'); npmlog.log('ffdc', opts.clientId, process.env); } npmlog.log('ffdc', opts.clientId, ''); } // In a unit testing environment we expect to get no ffdcs. if (process.env.NODE_ENV === 'unittest') { var err = new Error('No ffdcs expected during unit tests'); logger.throw('logger.ffdc', opts.clientId, err); throw err; } logger.exit('logger.ffdc', opts.clientId, null); // Exit if fail on FFDC is required. if (process.env.MQLIGHT_NODE_FAIL_ON_FFDC) { console.error('Aborting due to FFDC'); process.abort(); } }; /** * The identifier used when a log entry is not associated with a * particular client. * * @const {string} */ logger.NO_CLIENT_ID = '*'; npmlog.addLevel('all', -Infinity, styles.inverse, 'all '); npmlog.addLevel('proton_data', -Infinity, styles.green, 'data '); npmlog.addLevel('proton_exit', -Infinity, styles.yellow, 'exit '); npmlog.addLevel('proton_entry', -Infinity, styles.yellow, 'entry '); npmlog.addLevel('proton', -Infinity, styles.yellow, 'func '); npmlog.addLevel('data_often', 100, styles.green, 'data '); npmlog.addLevel('exit_often', 100, styles.yellow, 'exit '); npmlog.addLevel('entry_often', 100, styles.yellow, 'entry '); npmlog.addLevel('often', 100, styles.yellow, 'func '); npmlog.addLevel('raw', 200, styles.inverse, 'raw '); npmlog.addLevel('detail', 300, styles.green, 'detail'); npmlog.addLevel('debug', 500, styles.inverse, 'debug '); npmlog.addLevel('emit', 800, styles.green, 'emit '); npmlog.addLevel('data', 1000, styles.green, 'data '); npmlog.addLevel('parms', 1200, styles.yellow, 'parms '); npmlog.addLevel('header', 1500, styles.yellow, 'header'); npmlog.addLevel('exit', 1500, styles.yellow, 'exit '); npmlog.addLevel('entry', 1500, styles.yellow, 'entry '); npmlog.addLevel('entry_exit', 1500, styles.yellow, 'func '); npmlog.addLevel('error', 1800, styles.red, 'error '); npmlog.addLevel('object', 1900, styles.red, 'object'); npmlog.addLevel('ffdc', 2000, styles.red, 'ffdc '); /** * Set the logging stream. By default stderr will be used, but * this can be changed to stdout by setting the environment * variable MQLIGHT_NODE_LOG_STREAM=stdout. */ logger.setStream(process.env.MQLIGHT_NODE_LOG_STREAM || 'stderr'); /** * Set the amount of message data that will get logged. The * default is 100 bytes, but this can be altered by setting the * environment variable MQLIGHT_NODE_MESSAGE_DATA_SIZE to a * different number. */ logger.setDataSize(process.env.MQLIGHT_NODE_MESSAGE_DATA_SIZE || 100); /** * Set the level of logging. By default only 'ffdc' entries will * be logged, but this can be altered by setting the environment * variable MQLIGHT_NODE_LOG to one of the defined levels. */ startLevel = process.env.MQLIGHT_NODE_LOG || 'ffdc'; logger.setLevel(startLevel); /** * Set the maximum size of logging history. By default a maximum * of 10,000 entries will be retained, but this can be altered * by setting the environment variable * MQLIGHT_NODE_LOG_HISTORY_SIZE to a different number. */ npmlog.maxRecordSize = process.env.MQLIGHT_NODE_LOG_HISTORY_SIZE || 10000; logger.log('debug', logger.NO_CLIENT_ID, 'npmlog.maxRecordSize:', npmlog.maxRecordSize); /* * Set the level of entries that will dumped in the ffdc function history. * By default only entries at debug level or above will be dumped, but this can * be altered by setting the environment variable MQLIGHT_NODE_LOG_HISTORY to * one of the defined levels. */ historyLevel = process.env.MQLIGHT_NODE_LOG_HISTORY || 'debug'; logger.log('debug', logger.NO_CLIENT_ID, 'historyLevel:', historyLevel); /* * Set up a signal handler that will cause an ffdc to be generated when * the signal is caught. Set the environment variable MQLIGHT_NODE_NO_HANDLER * to stop the signal handler being registered. */ if (!process.env.MQLIGHT_NODE_NO_HANDLER) { var signal = isWin ? 'SIGBREAK' : 'SIGUSR2'; logger.log('debug', logger.NO_CLIENT_ID, 'Registering signal handler for', signal); process.on(signal, function() { logger.ffdc(signal, 255, null, 'User-requested FFDC on signal'); // Start logging at the 'debug' level if we're not doing so, or turn off // logging if we already are. if (npmlog.levels[startLevel] > npmlog.levels.debug) { if (npmlog.level === startLevel) { logger.log('ffdc', logger.NO_CLIENT_ID, 'Setting npmlog.level: debug'); logger.setLevel('debug'); } else { logger.log('ffdc', logger.NO_CLIENT_ID, 'Setting npmlog.level:', startLevel); logger.setLevel(startLevel); } } }); } if (process.env.MQLIGHT_NODE_DEBUG_PORT) { /** * Set the port which the debugger will listen on to the value of the * MQLIGHT_NODE_DEBUG_PORT environment variable, if it's set. */ process.debugPort = process.env.MQLIGHT_NODE_DEBUG_PORT; } /* * If the MQLIGHT_NODE_FFDC_ON_UNCAUGHT environment variable is set, then * an ffdc will be produced on an uncaught exception. */ if (process.env.MQLIGHT_NODE_FFDC_ON_UNCAUGHT) { logger.log('debug', logger.NO_CLIENT_ID, 'Registering uncaught exception handler'); process.on('uncaughtException', function(err) { logger.ffdc('uncaughtException', 100, null, err); throw err; }); }