file.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. 'use strict';
  2. const fs = require('fs');
  3. const path = require('path');
  4. const assert = require('assert');
  5. const mkdirp = require('mkdirp');
  6. const utility = require('utility');
  7. const depd = require('depd')('egg-logger');
  8. const Transport = require('./transport');
  9. const utils = require('../utils');
  10. /**
  11. * output log into file {@link Transport}。
  12. */
  13. class FileTransport extends Transport {
  14. /**
  15. * @class
  16. * @param {Object} options
  17. * - {String} file - file path
  18. * - {String} [level = INFO] - log level
  19. */
  20. constructor(options) {
  21. super(options);
  22. assert(this.options.file, 'should pass options.file');
  23. this._stream = null;
  24. this.reload();
  25. }
  26. get defaults() {
  27. return utils.assign(super.defaults, {
  28. file: null,
  29. level: 'INFO',
  30. });
  31. }
  32. /**
  33. * reload file stream
  34. */
  35. reload() {
  36. this._closeStream();
  37. this._stream = this._createStream();
  38. }
  39. /**
  40. * output log, see {@link Transport#log}
  41. * @param {String} level - log level
  42. * @param {Array} args - all arguments
  43. * @param {Object} meta - meta information
  44. */
  45. log(level, args, meta) {
  46. if (!this.writable) {
  47. const err = new Error(`${this.options.file} log stream had been closed`);
  48. console.error(err.stack);
  49. return;
  50. }
  51. const buf = super.log(level, args, meta);
  52. if (buf.length) {
  53. this._write(buf);
  54. }
  55. }
  56. /**
  57. * close stream
  58. */
  59. close() {
  60. this._closeStream();
  61. }
  62. /**
  63. * @deprecated
  64. */
  65. end() {
  66. depd('transport.end() is deprecated, use transport.close()');
  67. this.close();
  68. }
  69. /**
  70. * write stream directly
  71. * @param {Buffer|String} buf - log content
  72. * @private
  73. */
  74. _write(buf) {
  75. this._stream.write(buf);
  76. }
  77. /**
  78. * transport is writable
  79. * @return {Boolean} writable
  80. */
  81. get writable() {
  82. return this._stream && !this._stream.closed && this._stream.writable && !this._stream.destroyed;
  83. }
  84. /**
  85. * create stream
  86. * @return {Stream} return writeStream
  87. * @private
  88. */
  89. _createStream() {
  90. mkdirp.sync(path.dirname(this.options.file));
  91. const stream = fs.createWriteStream(this.options.file, { flags: 'a' });
  92. const onError = err => {
  93. console.error('%s ERROR %s [egg-logger] [%s] %s',
  94. utility.logDate(','), process.pid, this.options.file, err.stack);
  95. this.reload();
  96. console.warn('%s WARN %s [egg-logger] [%s] reloaded', utility.logDate(','), process.pid, this.options.file);
  97. };
  98. // only listen error once because stream will reload after error
  99. stream.once('error', onError);
  100. stream._onError = onError;
  101. return stream;
  102. }
  103. /**
  104. * close stream
  105. * @private
  106. */
  107. _closeStream() {
  108. if (this._stream) {
  109. this._stream.end();
  110. this._stream.removeListener('error', this._stream._onError);
  111. this._stream = null;
  112. }
  113. }
  114. }
  115. module.exports = FileTransport;