logger.js (153 lines of code) (raw):

// Copyright (c) 2015 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. var extend = require('xtend'); var EventEmitter = require('events').EventEmitter; var inherits = require('inherits'); var collectParallel = require('collect-parallel/object'); var parallelWrite = require('./lib/parallel-write.js'); var defaultLevels = require('./default-levels.js'); var serializableErrorTransform = require('./transforms/serialize-error.js'); var safeSerializeMeta = require('./transforms/safe-serialize-meta.js'); var writePidAndHost = require('./transforms/pid-and-host.js'); var errors = require('./errors.js'); var makeLogMethod = require('./log-method'); var ChildLogger = require('./child-logger'); function Logger(opts) { if (!(this instanceof Logger)) { return new Logger(opts); } var self = this; if (!opts) { throw errors.OptsRequired(); } if (!opts.meta) { throw errors.MetaRequired(); } if (!opts.backends) { throw errors.BackendsRequired(); } EventEmitter.call(this); var meta = this.meta = opts.meta; var transforms = opts.transforms || []; var basemetaTransforms = opts.basemetaTransforms || []; basemetaTransforms.forEach(function initTransforms(transform) { transforms.push(transform(meta)); }); transforms.push(safeSerializeMeta); transforms.push(serializableErrorTransform); transforms.push(writePidAndHost(meta)); this.statsd = opts.statsd; this.path = opts.path = ""; // Performs a deep copy of the default log levels, overlaying the // configured levels and filtering nulled levels. var levels = this.levels = {}; var configuredLevels = extend(defaultLevels, opts.levels || {}); Object.keys(configuredLevels) .forEach(function copyDefaultLevel(levelName) { // Setting a level in opts.levels to null disables that level. if (!configuredLevels[levelName]) { return; } // Each log level will contain an array of transforms by default, // that will be suffixed with globally configured transforms. var level = extend({transforms: []}, configuredLevels[levelName]); level.transforms = level.transforms.concat(transforms); levels[levelName] = level; }); // Create a log level method, e.g., info(message, meta, cb?), for every // configured log level. Object.keys(levels) .forEach(function makeMethodForLevel(levelName) { self[levelName] = makeLogMethod(levelName); }); // Create a stream for each of the configured backends, indexed by backend // name. // streams: Object<backendName, Stream> var streams = this.streams = Object.keys(opts.backends) .reduce(function accumulateStreams(streamByBackend, backendName) { var backend = opts.backends[backendName]; if (!backend) { return streamByBackend; } streamByBackend[backendName] = backend.createStream(meta, { highWaterMark: opts.highWaterMark || 1000 }); return streamByBackend; }, {}); // Creates an index of all the streams that each log level will write to, // keyed by log level. // The index is used by the writeEntry method to look up all the target // streams for the given level. // The parallel write method uses the backend name to annotate errors. // _streamsByLevel: Object<logLevel, Array<Object<backendName, Stream>>> this._streamsByLevel = Object.keys(levels) .reduce(function accumulateStreamsByLevel(streamsByLevel, levelName) { if (!levels[levelName]) { return streamsByLevel; } var level = levels[levelName]; streamsByLevel[levelName] = level.backends .reduce(function accumulateStreamsByBackend( levelStreams, backendName ) { if (streams[backendName]) { levelStreams.push({ name: backendName, stream: streams[backendName] }); } return levelStreams; }, []); return streamsByLevel; }, {}); } inherits(Logger, EventEmitter); Logger.prototype.instrument = function instrument() { }; Logger.prototype.close = function close(callback) { collectParallel(this.streams, closeEachStream, finish); function closeEachStream(stream, i, done) { if (stream && stream.close) { stream.close(done); } } function finish(err, results) { for (var i = 0; i < results; i++) { if (results[i].err) { callback(results[i].err); return; } } callback(null); } }; Logger.prototype.destroy = function destroy() { Object.keys(this.streams).forEach(function destroyStreamForLevel(name) { var stream = this.streams[name]; if (stream && stream.destroy) { stream.destroy(); } }, this); }; Logger.prototype.writeEntry = function writeEntry(entry, callback) { // Apply transforms before grabbing streams for the given level, since transforms // may change the log level. var transforms = this.levels[entry.level].transforms; for (var i=0; i<transforms.length; ++i) { entry = transforms[i](entry); } var levelName = entry.level; var logStreams = this._streamsByLevel[levelName]; var logger = this; if (this.statsd && typeof this.statsd.increment === 'function') { this.statsd.increment('logtron.logged.' + levelName); } parallelWrite(logStreams, entry, function (err) { if (!err) { if (callback) { callback(null); } return; } if (callback && typeof callback === 'function') { return callback(err); } logger.emit('error', err); }); }; Logger.prototype.createChild = function createChild(path, levels, opts, mainLogger) { opts = opts || {}; return new ChildLogger({ mainLogger: mainLogger || this, path: path, levels: levels, extendMeta: opts.extendMeta, meta: opts.meta, strict: opts.strict, metaFilter: opts.metaFilter, mergeParentMeta: opts.mergeParentMeta }); }; module.exports = Logger;