logstream.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /**!
  2. * logstream - lib/logstream.js
  3. *
  4. * Copyright(c) 2013 fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
  5. * MIT Licensed
  6. */
  7. "use strict";
  8. /**
  9. * Module dependencies.
  10. */
  11. var fs = require('fs');
  12. var path = require('path');
  13. var EventEmitter = require('events').EventEmitter;
  14. var util = require('util');
  15. var moment = require('moment');
  16. var mkdirp = require('mkdirp');
  17. var iconv = require('iconv-lite');
  18. var ONE_MINUTE = 60000;
  19. var ONE_HOUR = 60 * ONE_MINUTE;
  20. var ONE_DAY = 24 * ONE_HOUR;
  21. /*!
  22. * Default log buffer duration.
  23. */
  24. var defaultBufferDuration = 1000;
  25. /**
  26. * Log stream, auto cut the log file.
  27. *
  28. * log file name is concat with `prename + format + ext`.
  29. *
  30. * @param {Object} options
  31. * - {String} logdir, this dir must exists.
  32. * - {String} nameformat, default is '[info.]YYYY-MM-DD[.log]',
  33. * @see moment().format(): http://momentjs.com/docs/#/displaying/format/
  34. * Also support '{pid}' for process pid.
  35. * - {String} [encoding], default is utf-8, other encoding will encode by iconv-lite
  36. * - {Number} [duration], default is one houre(24 * 3600000 ms), must >= 60s.
  37. * - {String} [mode], default is '0666'.
  38. * - {Number} [buffer] buffer duration, default is 1000ms
  39. * - {Boolean} [mkdir] try to mkdir in each cut, make sure dir exist.
  40. * useful when your nameformat like 'YYYY/MM/DD/[info.log]'.
  41. * return {LogStream}
  42. */
  43. module.exports = function createStream(options) {
  44. return new LogStream(options);
  45. };
  46. function LogStream(options) {
  47. if (!(this instanceof LogStream)) {
  48. return new LogStream(options);
  49. }
  50. options = options || {};
  51. this.logdir = options.logdir;
  52. this.nameformat = options.nameformat || '[info.]YYYY-MM-DD[.log]';
  53. this.nameformat = this.nameformat.replace('{pid}', process.pid);
  54. this.duration = options.duration || ONE_HOUR;
  55. // must >= one minute
  56. if (this.duration < 60000) {
  57. this.duration = 60000;
  58. }
  59. this.encoding = (options.encoding || 'utf-8').toLowerCase();
  60. if (this.encoding === 'utf8') {
  61. this.encoding = 'utf-8';
  62. }
  63. this.streamMode = options.mode || '0666';
  64. this.mkdir = options.mkdir;
  65. this.cut();
  66. this.startTimer(this.firstDuration());
  67. this._buf = [];
  68. this._flushInterval = options.buffer || defaultBufferDuration;
  69. setInterval(this._flush.bind(this), this._flushInterval);
  70. }
  71. util.inherits(LogStream, EventEmitter);
  72. LogStream.prototype.firstDuration = function () {
  73. var firstDuration = this.duration;
  74. if (this.duration > ONE_MINUTE) {
  75. var now = moment();
  76. if (this.duration < ONE_HOUR) { // in minute
  77. firstDuration = now.clone().add('ms', this.duration).startOf('minute').diff(now);
  78. } else if (this.duration < ONE_DAY) { // in hour
  79. firstDuration = now.clone().add('ms', this.duration).startOf('hour').diff(now);
  80. } else { // in day
  81. firstDuration = now.clone().add('ms', this.duration).startOf('day').diff(now);
  82. }
  83. }
  84. return firstDuration;
  85. };
  86. LogStream.prototype.startTimer = function (duration) {
  87. this._timer = setTimeout(function (self) {
  88. self.cut();
  89. self.startTimer(self.duration);
  90. }, duration || this.duration, this);
  91. };
  92. LogStream.prototype.cut = function () {
  93. if (this.stream) {
  94. this._flush();
  95. this.stream.end();
  96. this.stream.destroySoon();
  97. this.stream = null;
  98. }
  99. var name = moment().format(this.nameformat);
  100. var logpath = path.join(this.logdir, name);
  101. // make sure dir exist
  102. if (this.mkdir) {
  103. try {
  104. mkdirp.sync(path.dirname(logpath));
  105. } catch (err) {
  106. // ignore
  107. }
  108. }
  109. this._reopening = true;
  110. this.stream = fs.createWriteStream(logpath, {flags: 'a', mode: this.streamMode});
  111. this.stream
  112. .on("error", this.emit.bind(this, "error"))
  113. .on("pipe", this.emit.bind(this, "pipe"))
  114. .on("drain", this.emit.bind(this, "drain"))
  115. .on("open", function () {
  116. this._reopening = false;
  117. }.bind(this))
  118. .on("close", function () {
  119. if (!this._reopening) {
  120. this.emit("close");
  121. }
  122. }.bind(this));
  123. };
  124. LogStream.prototype.write = function (string) {
  125. this._buf.push(string);
  126. };
  127. LogStream.prototype.flush =
  128. LogStream.prototype._flush = function () {
  129. if (this._buf.length) {
  130. var buf = this._encode(this._buf.join(''));
  131. this.stream.write(buf);
  132. this._buf.length = 0;
  133. }
  134. };
  135. LogStream.prototype._encode = function (string) {
  136. if (this.encoding === 'utf-8') {
  137. return new Buffer(string);
  138. }
  139. return iconv.encode(string, this.encoding);
  140. };
  141. LogStream.prototype.end = function () {
  142. if (this._timer) {
  143. clearTimeout(this._timer);
  144. this._timer = null;
  145. }
  146. if (this.stream) {
  147. this._flush();
  148. this.stream.end();
  149. this.stream.destroySoon();
  150. this.stream = null;
  151. }
  152. };
  153. LogStream.prototype.close = LogStream.prototype.end;
  154. module.exports.LogStream = LogStream;