cocalc/src / smc-project / node_modules / forever / node_modules / winston / lib / winston / transports / file.js
50665 views/*1* file.js: Transport for outputting to a local log file2*3* (C) 2010 Charlie Robbins4* MIT LICENCE5*6*/78var events = require('events'),9fs = require('fs'),10path = require('path'),11util = require('util'),12colors = require('colors'),13common = require('../common'),14Transport = require('./transport').Transport,15Stream = require('stream').Stream;1617//18// ### function File (options)19// #### @options {Object} Options for this instance.20// Constructor function for the File transport object responsible21// for persisting log messages and metadata to one or more files.22//23var File = exports.File = function (options) {24Transport.call(this, options);2526//27// Helper function which throws an `Error` in the event28// that any of the rest of the arguments is present in `options`.29//30function throwIf (target /*, illegal... */) {31Array.prototype.slice.call(arguments, 1).forEach(function (name) {32if (options[name]) {33throw new Error('Cannot set ' + name + ' and ' + target + 'together');34}35});36}3738if (options.filename || options.dirname) {39throwIf('filename or dirname', 'stream');40this._basename = this.filename = path.basename(options.filename) || 'winston.log';41this.dirname = options.dirname || path.dirname(options.filename);42this.options = options.options || { flags: 'a' };43}44else if (options.stream) {45throwIf('stream', 'filename', 'maxsize');46this._stream = options.stream;4748//49// We need to listen for drain events when50// write() returns false. This can make node51// mad at times.52//53this._stream.setMaxListeners(Infinity);54}55else {56throw new Error('Cannot log to file without filename or stream.');57}5859this.json = options.json !== false;60this.colorize = options.colorize || false;61this.maxsize = options.maxsize || null;62this.maxFiles = options.maxFiles || null;63this.prettyPrint = options.prettyPrint || false;64this.timestamp = options.timestamp != null ? options.timestamp : true;6566if (this.json) {67this.stringify = options.stringify;68}6970//71// Internal state variables representing the number72// of files this instance has created and the current73// size (in bytes) of the current logfile.74//75this._size = 0;76this._created = 0;77this._buffer = [];78this._draining = false;79};8081//82// Inherit from `winston.Transport`.83//84util.inherits(File, Transport);8586//87// Expose the name of this Transport on the prototype88//89File.prototype.name = 'file';9091//92// ### function log (level, msg, [meta], callback)93// #### @level {string} Level at which to log the message.94// #### @msg {string} Message to log95// #### @meta {Object} **Optional** Additional metadata to attach96// #### @callback {function} Continuation to respond to when complete.97// Core logging method exposed to Winston. Metadata is optional.98//99File.prototype.log = function (level, msg, meta, callback) {100if (this.silent) {101return callback(null, true);102}103104var self = this;105106var output = common.log({107level: level,108message: msg,109meta: meta,110json: this.json,111colorize: this.colorize,112prettyPrint: this.prettyPrint,113timestamp: this.timestamp,114stringify: this.stringify115}) + '\n';116117this._size += output.length;118119if (!this.filename) {120//121// If there is no `filename` on this instance then it was configured122// with a raw `WriteableStream` instance and we should not perform any123// size restrictions.124//125this._write(output, callback);126this._lazyDrain();127}128else {129this.open(function (err) {130if (err) {131//132// If there was an error enqueue the message133//134return self._buffer.push([output, callback]);135}136137self._write(output, callback);138self._lazyDrain();139});140}141};142143//144// ### function _write (data, cb)145// #### @data {String|Buffer} Data to write to the instance's stream.146// #### @cb {function} Continuation to respond to when complete.147// Write to the stream, ensure execution of a callback on completion.148//149File.prototype._write = function(data, callback) {150// If this is a file write stream, we could use the builtin151// callback functionality, however, the stream is not guaranteed152// to be an fs.WriteStream.153var ret = this._stream.write(data);154if (!callback) return;155if (ret === false) {156return this._stream.once('drain', function() {157callback(null, true);158});159}160callback(null, true);161};162163//164// ### function query (options, callback)165// #### @options {Object} Loggly-like query options for this instance.166// #### @callback {function} Continuation to respond to when complete.167// Query the transport. Options object is optional.168//169File.prototype.query = function (options, callback) {170if (typeof options === 'function') {171callback = options;172options = {};173}174175var file = path.join(this.dirname, this.filename),176options = this.normalizeQuery(options),177buff = '',178results = [],179row = 0;180181var stream = fs.createReadStream(file, {182encoding: 'utf8'183});184185stream.on('error', function (err) {186if (stream.readable) {187stream.destroy();188}189if (!callback) return;190return err.code !== 'ENOENT'191? callback(err)192: callback(null, results);193});194195stream.on('data', function (data) {196var data = (buff + data).split(/\n+/),197l = data.length - 1,198i = 0;199200for (; i < l; i++) {201if (!options.start || row >= options.start) {202add(data[i]);203}204row++;205}206207buff = data[l];208});209210stream.on('close', function () {211if (buff) add(buff, true);212if (options.order === 'desc') {213results = results.reverse();214}215if (callback) callback(null, results);216});217218function add(buff, attempt) {219try {220var log = JSON.parse(buff);221if (check(log)) push(log);222} catch (e) {223if (!attempt) {224stream.emit('error', e);225}226}227}228229function push(log) {230if (options.rows && results.length >= options.rows) {231if (stream.readable) {232stream.destroy();233}234return;235}236237if (options.fields) {238var obj = {};239options.fields.forEach(function (key) {240obj[key] = log[key];241});242log = obj;243}244245results.push(log);246}247248function check(log) {249if (!log) return;250251if (typeof log !== 'object') return;252253var time = new Date(log.timestamp);254if ((options.from && time < options.from)255|| (options.until && time > options.until)) {256return;257}258259return true;260}261};262263//264// ### function _tail (options, callback)265// #### @options {Object} Options for tail.266// #### @callback {function} Callback to execute on every line.267// `tail -f` a file. Options must include file.268//269File.prototype._tail = function tail(options, callback) {270var stream = fs.createReadStream(options.file, { encoding: 'utf8' }),271buff = '',272destroy,273row = 0;274275destroy = stream.destroy.bind(stream);276stream.destroy = function () {};277278if (options.start === -1) {279delete options.start;280}281282if (options.start == null) {283stream.once('end', bind);284} else {285bind();286}287288function bind() {289stream.on('data', function (data) {290var data = (buff + data).split(/\n+/),291l = data.length - 1,292i = 0;293294for (; i < l; i++) {295if (options.start == null || row > options.start) {296stream.emit('line', data[i]);297}298row++;299}300301buff = data[l];302});303304stream.on('line', function (data) {305if (callback) callback(data);306});307308stream.on('error', function (err) {309destroy();310});311312stream.on('end', function () {313if (buff) {314stream.emit('line', buff);315buff = '';316}317318resume();319});320321resume();322}323324function resume() {325setTimeout(function () {326stream.resume();327}, 1000);328}329330return destroy;331};332333//334// ### function stream (options)335// #### @options {Object} Stream options for this instance.336// Returns a log stream for this transport. Options object is optional.337//338File.prototype.stream = function (options) {339var file = path.join(this.dirname, this.filename),340options = options || {},341stream = new Stream;342343var tail = {344file: file,345start: options.start346};347348stream.destroy = this._tail(tail, function (line) {349try {350stream.emit('data', line);351line = JSON.parse(line);352stream.emit('log', line);353} catch (e) {354stream.emit('error', e);355}356});357358return stream;359};360361//362// ### function open (callback)363// #### @callback {function} Continuation to respond to when complete364// Checks to see if a new file needs to be created based on the `maxsize`365// (if any) and the current size of the file used.366//367File.prototype.open = function (callback) {368if (this.opening) {369//370// If we are already attempting to open the next371// available file then respond with a value indicating372// that the message should be buffered.373//374return callback(true);375}376else if (!this._stream || (this.maxsize && this._size >= this.maxsize)) {377//378// If we dont have a stream or have exceeded our size, then create379// the next stream and respond with a value indicating that380// the message should be buffered.381//382callback(true);383return this._createStream();384}385386//387// Otherwise we have a valid (and ready) stream.388//389callback();390};391392//393// ### function close ()394// Closes the stream associated with this instance.395//396File.prototype.close = function () {397var self = this;398399if (this._stream) {400this._stream.end();401this._stream.destroySoon();402403this._stream.once('drain', function () {404self.emit('flush');405self.emit('closed');406});407}408};409410//411// ### function flush ()412// Flushes any buffered messages to the current `stream`413// used by this instance.414//415File.prototype.flush = function () {416var self = this;417418//419// Iterate over the `_buffer` of enqueued messaged420// and then write them to the newly created stream.421//422this._buffer.forEach(function (item) {423var str = item[0],424callback = item[1];425426process.nextTick(function () {427self._write(str, callback);428self._size += str.length;429});430});431432//433// Quickly truncate the `_buffer` once the write operations434// have been started435//436self._buffer.length = 0;437438//439// When the stream has drained we have flushed440// our buffer.441//442self._stream.once('drain', function () {443self.emit('flush');444self.emit('logged');445});446};447448//449// ### @private function _createStream ()450// Attempts to open the next appropriate file for this instance451// based on the common state (such as `maxsize` and `_basename`).452//453File.prototype._createStream = function () {454var self = this;455this.opening = true;456457(function checkFile (target) {458var fullname = path.join(self.dirname, target);459460//461// Creates the `WriteStream` and then flushes any462// buffered messages.463//464function createAndFlush (size) {465if (self._stream) {466self._stream.end();467self._stream.destroySoon();468}469470self._size = size;471self.filename = target;472self._stream = fs.createWriteStream(fullname, self.options);473474//475// We need to listen for drain events when476// write() returns false. This can make node477// mad at times.478//479self._stream.setMaxListeners(Infinity);480481//482// When the current stream has finished flushing483// then we can be sure we have finished opening484// and thus can emit the `open` event.485//486self.once('flush', function () {487self.opening = false;488self.emit('open', fullname);489});490491//492// Remark: It is possible that in the time it has taken to find the493// next logfile to be written more data than `maxsize` has been buffered,494// but for sensible limits (10s - 100s of MB) this seems unlikely in less495// than one second.496//497self.flush();498}499500fs.stat(fullname, function (err, stats) {501if (err) {502if (err.code !== 'ENOENT') {503return self.emit('error', err);504}505506return createAndFlush(0);507}508509if (!stats || (self.maxsize && stats.size >= self.maxsize)) {510//511// If `stats.size` is greater than the `maxsize` for512// this instance then try again513//514return checkFile(self._getFile(true));515}516517createAndFlush(stats.size);518});519})(this._getFile());520};521522//523// ### @private function _getFile ()524// Gets the next filename to use for this instance525// in the case that log filesizes are being capped.526//527File.prototype._getFile = function (inc) {528var self = this,529ext = path.extname(this._basename),530basename = path.basename(this._basename, ext),531remaining;532533if (inc) {534//535// Increment the number of files created or536// checked by this instance.537//538// Check for maxFiles option and delete file539if (this.maxFiles && (this._created >= (this.maxFiles - 1))) {540remaining = this._created - (this.maxFiles - 1);541if (remaining === 0) {542fs.unlinkSync(path.join(this.dirname, basename + ext));543}544else {545fs.unlinkSync(path.join(this.dirname, basename + remaining + ext));546}547}548549this._created += 1;550}551552return this._created553? basename + this._created + ext554: basename + ext;555};556557//558// ### @private function _lazyDrain ()559// Lazily attempts to emit the `logged` event when `this.stream` has560// drained. This is really just a simple mutex that only works because561// Node.js is single-threaded.562//563File.prototype._lazyDrain = function () {564var self = this;565566if (!this._draining && this._stream) {567this._draining = true;568569this._stream.once('drain', function () {570this._draining = false;571self.emit('logged');572});573}574};575576577