/* * common.js: Internal helper and utility functions for winston * * (C) 2010 Charlie Robbins * MIT LICENCE * */ var util = require('util'), crypto = require('crypto'), cycle = require('cycle'), fs = require('fs'), config = require('./config'); // // ### function setLevels (target, past, current) // #### @target {Object} Object on which to set levels. // #### @past {Object} Previous levels set on target. // #### @current {Object} Current levels to set on target. // Create functions on the target objects for each level // in current.levels. If past is defined, remove functions // for each of those levels. // exports.setLevels = function (target, past, current, isDefault) { if (past) { Object.keys(past).forEach(function (level) { delete target[level]; }); } target.levels = current || config.npm.levels; if (target.padLevels) { target.levelLength = exports.longestElement(Object.keys(target.levels)); } // // Define prototype methods for each log level // e.g. target.log('info', msg) <=> target.info(msg) // Object.keys(target.levels).forEach(function (level) { target[level] = function (msg) { // build argument list (level, msg, ... [string interpolate], [{metadata}], [callback]) var args = [level].concat(Array.prototype.slice.call(arguments)); target.log.apply(target, args); }; }); return target; }; // // ### function longestElement // #### @xs {Array} Array to calculate against // Returns the longest element in the `xs` array. // exports.longestElement = function (xs) { return Math.max.apply( null, xs.map(function (x) { return x.length; }) ); }; // // ### function clone (obj) // #### @obj {Object} Object to clone. // Helper method for deep cloning pure JSON objects // i.e. JSON objects that are either literals or objects (no Arrays, etc) // exports.clone = function (obj) { // we only need to clone refrence types (Object) if (!(obj instanceof Object)) { return obj; } else if (obj instanceof Date) { return obj; } var copy = {}; for (var i in obj) { if (Array.isArray(obj[i])) { copy[i] = obj[i].slice(0); } else if (obj[i] instanceof Buffer) { copy[i] = obj[i].slice(0); } else if (typeof obj[i] != 'function') { copy[i] = obj[i] instanceof Object ? exports.clone(obj[i]) : obj[i]; } else if (typeof obj[i] === 'function') { copy[i] = obj[i]; } } return copy; }; // // ### function log (options) // #### @options {Object} All information about the log serialization. // Generic logging function for returning timestamped strings // with the following options: // // { // level: 'level to add to serialized message', // message: 'message to serialize', // meta: 'additional logging metadata to serialize', // colorize: false, // Colorizes output (only if `.json` is false) // timestamp: true // Adds a timestamp to the serialized message // label: 'label to prepend the message' // } // exports.log = function (options) { var timestampFn = typeof options.timestamp === 'function' ? options.timestamp : exports.timestamp, timestamp = options.timestamp ? timestampFn() : null, meta = options.meta !== undefined || options.meta !== null ? exports.clone(cycle.decycle(options.meta)) : null, output; // // raw mode is intended for outputing winston as streaming JSON to STDOUT // if (options.raw) { if (typeof meta !== 'object' && meta != null) { meta = { meta: meta }; } output = exports.clone(meta) || {}; output.level = options.level; output.message = options.message.stripColors; return JSON.stringify(output); } // // json mode is intended for pretty printing multi-line json to the terminal // if (options.json) { if (typeof meta !== 'object' && meta != null) { meta = { meta: meta }; } output = exports.clone(meta) || {}; output.level = options.level; output.message = options.message; if (timestamp) { output.timestamp = timestamp; } if (options.logstash === true) { // use logstash format var logstashOutput = {}; if (output.message !== undefined) { logstashOutput['@message'] = output.message; delete output.message; } if (output.timestamp !== undefined) { logstashOutput['@timestamp'] = output.timestamp; delete output.timestamp; } logstashOutput['@fields'] = exports.clone(output); output = logstashOutput; } if (typeof options.stringify === 'function') { return options.stringify(output); } return JSON.stringify(output, function (key, value) { return value instanceof Buffer ? value.toString('base64') : value; }); } output = timestamp ? timestamp + ' - ' : ''; output += options.colorize ? config.colorize(options.level) : options.level; output += ': '; output += options.label ? ('[' + options.label + '] ') : ''; output += options.message; if (meta !== null && meta !== undefined) { if (meta && meta instanceof Error && meta.stack) { meta = meta.stack; } if (typeof meta !== 'object') { output += ' ' + meta; } else if (Object.keys(meta).length > 0) { output += ' ' + ( options.prettyPrint ? ('\n' + util.inspect(meta, false, null, options.colorize)) : exports.serialize(meta) ); } } return output; }; exports.capitalize = function (str) { return str && str[0].toUpperCase() + str.slice(1); }; // // ### function hash (str) // #### @str {string} String to hash. // Utility function for creating unique ids // e.g. Profiling incoming HTTP requests on the same tick // exports.hash = function (str) { return crypto.createHash('sha1').update(str).digest('hex'); }; // // ### function pad (n) // Returns a padded string if `n < 10`. // exports.pad = function (n) { return n < 10 ? '0' + n.toString(10) : n.toString(10); }; // // ### function timestamp () // Returns a timestamp string for the current time. // exports.timestamp = function () { return new Date().toISOString(); }; // // ### function serialize (obj, key) // #### @obj {Object|literal} Object to serialize // #### @key {string} **Optional** Optional key represented by obj in a larger object // Performs simple comma-separated, `key=value` serialization for Loggly when // logging to non-JSON inputs. // exports.serialize = function (obj, key) { if (obj === null) { obj = 'null'; } else if (obj === undefined) { obj = 'undefined'; } else if (obj === false) { obj = 'false'; } if (typeof obj !== 'object') { return key ? key + '=' + obj : obj; } if (obj instanceof Buffer) { return key ? key + '=' + obj.toString('base64') : obj.toString('base64'); } var msg = '', keys = Object.keys(obj), length = keys.length; for (var i = 0; i < length; i++) { if (Array.isArray(obj[keys[i]])) { msg += keys[i] + '=['; for (var j = 0, l = obj[keys[i]].length; j < l; j++) { msg += exports.serialize(obj[keys[i]][j]); if (j < l - 1) { msg += ', '; } } msg += ']'; } else if (obj[keys[i]] instanceof Date) { msg += keys[i] + '=' + obj[keys[i]]; } else { msg += exports.serialize(obj[keys[i]], keys[i]); } if (i < length - 1) { msg += ', '; } } return msg; }; // // ### function tailFile (options, callback) // #### @options {Object} Options for tail. // #### @callback {function} Callback to execute on every line. // `tail -f` a file. Options must include file. // exports.tailFile = function tail(options, callback) { var stream = fs.createReadStream(options.file, { encoding: 'utf8' }), buff = '', destroy, row = 0; destroy = stream.destroy.bind(stream); stream.destroy = function () {}; if (options.start === -1) { delete options.start; } stream.on('data', function (data) { var data = (buff + data).split(/\n+/), l = data.length - 1, i = 0; for (; i < l; i++) { if (options.start == null || row > options.start) { callback(null, data[i]); } row++; } buff = data[l]; }); stream.on('error', function (err) { callback(err); destroy(); }); stream.on('end', function () { if (buff) { stream.emit('line', buff); buff = ''; } resume(); }); function resume() { setTimeout(function () { stream.resume(); }, 1000); } return destroy; };