|
| 1 | +'use strict'; |
| 2 | + |
| 3 | +var fs = require('fs'); |
| 4 | +var os = require('os'); |
| 5 | +var path = require('path'); |
| 6 | +var util = require('util'); |
| 7 | +var semver = require('semver'); |
| 8 | +var zlib = require('zlib'); |
| 9 | +var winston = require('winston'); |
| 10 | +var compat = require('winston-compat'); |
| 11 | +var MESSAGE = require('triple-beam').MESSAGE; |
| 12 | +var PassThrough = require('stream').PassThrough; |
| 13 | +var Transport = semver.major(winston.version) === 2 ? compat.Transport : require('winston-transport'); |
| 14 | + |
| 15 | +var loggerDefaults = { |
| 16 | + json: false, |
| 17 | + colorize: false, |
| 18 | + eol: os.EOL, |
| 19 | + logstash: null, |
| 20 | + prettyPrint: false, |
| 21 | + label: null, |
| 22 | + stringify: false, |
| 23 | + depth: null, |
| 24 | + showLevel: true, |
| 25 | + timestamp: function () { |
| 26 | + return new Date().toISOString(); |
| 27 | + } |
| 28 | +}; |
| 29 | + |
| 30 | +var DailyRotateFile = function (options) { |
| 31 | + options = options || {}; |
| 32 | + Transport.call(this, options); |
| 33 | + |
| 34 | + function throwIf(target /* , illegal... */) { |
| 35 | + Array.prototype.slice.call(arguments, 1).forEach(function (name) { |
| 36 | + if (options[name]) { |
| 37 | + throw new Error('Cannot set ' + name + ' and ' + target + ' together'); |
| 38 | + } |
| 39 | + }); |
| 40 | + } |
| 41 | + |
| 42 | + function getMaxSize(size) { |
| 43 | + if (size && typeof size === 'string') { |
| 44 | + var _s = size.toLowerCase().match(/^((?:0\.)?\d+)([k|m|g])$/); |
| 45 | + if (_s) { |
| 46 | + return size; |
| 47 | + } |
| 48 | + } else if (size && Number.isInteger(size)) { |
| 49 | + var sizeK = Math.round(size / 1024); |
| 50 | + return sizeK === 0 ? '1k' : sizeK + 'k'; |
| 51 | + } |
| 52 | + return null; |
| 53 | + } |
| 54 | + |
| 55 | + this.options = Object.assign({}, loggerDefaults, options); |
| 56 | + |
| 57 | + if (options.stream) { |
| 58 | + throwIf('stream', 'filename', 'maxsize'); |
| 59 | + this.logStream = new PassThrough(); |
| 60 | + this.logStream.pipe(options.stream); |
| 61 | + } else { |
| 62 | + this.filename = options.filename ? path.basename(options.filename) : 'winston.log'; |
| 63 | + this.dirname = options.dirname || path.dirname(options.filename); |
| 64 | + |
| 65 | + var self = this; |
| 66 | + |
| 67 | + this.logStream = require('file-stream-rotator').getStream({ |
| 68 | + filename: path.join(this.dirname, this.filename), |
| 69 | + frequency: 'custom', |
| 70 | + date_format: options.datePattern ? options.datePattern : 'YYYY-MM-DD', |
| 71 | + verbose: false, |
| 72 | + size: getMaxSize(options.maxSize), |
| 73 | + max_logs: options.maxFiles |
| 74 | + }); |
| 75 | + |
| 76 | + this.logStream.on('rotate', function (oldFile, newFile) { |
| 77 | + self.emit('rotate', oldFile, newFile); |
| 78 | + }); |
| 79 | + |
| 80 | + if (options.zippedArchive) { |
| 81 | + this.logStream.on('rotate', function (oldFile) { |
| 82 | + var gzip = zlib.createGzip(); |
| 83 | + var inp = fs.createReadStream(oldFile); |
| 84 | + var out = fs.createWriteStream(oldFile + '.gz'); |
| 85 | + inp.pipe(gzip).pipe(out).on('finish', function () { |
| 86 | + fs.unlinkSync(oldFile); |
| 87 | + }); |
| 88 | + }); |
| 89 | + } |
| 90 | + } |
| 91 | +}; |
| 92 | + |
| 93 | +module.exports = DailyRotateFile; |
| 94 | + |
| 95 | +util.inherits(DailyRotateFile, Transport); |
| 96 | + |
| 97 | +DailyRotateFile.prototype.name = 'dailyRotateFile'; |
| 98 | + |
| 99 | +var noop = function () {}; |
| 100 | +if (semver.major(winston.version) === 2) { |
| 101 | + DailyRotateFile.prototype.log = function (level, msg, meta, callback) { |
| 102 | + callback = callback || noop; |
| 103 | + var options = Object.assign({}, this.options, { |
| 104 | + level: level, |
| 105 | + message: msg, |
| 106 | + meta: meta |
| 107 | + }); |
| 108 | + |
| 109 | + var output = compat.log(options) + options.eol; |
| 110 | + this.logStream.write(output); |
| 111 | + callback(null, true); |
| 112 | + }; |
| 113 | +} else { |
| 114 | + DailyRotateFile.prototype.normalizeQuery = compat.Transport.prototype.normalizeQuery; |
| 115 | + DailyRotateFile.prototype.log = function (info, callback) { |
| 116 | + callback = callback || noop; |
| 117 | + |
| 118 | + this.logStream.write(info[MESSAGE] + this.options.eol); |
| 119 | + this.emit('logged', info); |
| 120 | + callback(null, true); |
| 121 | + }; |
| 122 | +} |
| 123 | + |
| 124 | +DailyRotateFile.prototype.close = function () { |
| 125 | + var self = this; |
| 126 | + if (this.logStream) { |
| 127 | + this.logStream.end(function () { |
| 128 | + self.emit('finish'); |
| 129 | + }); |
| 130 | + } |
| 131 | +}; |
| 132 | + |
| 133 | +DailyRotateFile.prototype.query = function (options, callback) { |
| 134 | + if (typeof options === 'function') { |
| 135 | + callback = options; |
| 136 | + options = {}; |
| 137 | + } |
| 138 | + |
| 139 | + if (!this.filename) { |
| 140 | + throw new Error('query() may not be used when initializing with a stream'); |
| 141 | + } |
| 142 | + |
| 143 | + var self = this; |
| 144 | + var results = []; |
| 145 | + var row = 0; |
| 146 | + options = self.normalizeQuery(options); |
| 147 | + |
| 148 | + var logFiles = (function () { |
| 149 | + var fileRegex = new RegExp(self.filename.replace('%DATE%', '.*'), 'i'); |
| 150 | + return fs.readdirSync(self.dirname).filter(function (file) { |
| 151 | + return path.basename(file).match(fileRegex); |
| 152 | + }); |
| 153 | + })(); |
| 154 | + |
| 155 | + if (logFiles.length === 0 && callback) { |
| 156 | + callback(null, results); |
| 157 | + } |
| 158 | + |
| 159 | + (function processLogFile(file) { |
| 160 | + if (!file) { |
| 161 | + return; |
| 162 | + } |
| 163 | + var logFile = path.join(self.dirname, file); |
| 164 | + var buff = ''; |
| 165 | + |
| 166 | + var stream = fs.createReadStream(logFile, { |
| 167 | + encoding: 'utf8' |
| 168 | + }); |
| 169 | + |
| 170 | + stream.on('error', function (err) { |
| 171 | + if (stream.readable) { |
| 172 | + stream.destroy(); |
| 173 | + } |
| 174 | + if (!callback) { |
| 175 | + return; |
| 176 | + } |
| 177 | + return err.code === 'ENOENT' ? callback(null, results) : callback(err); |
| 178 | + }); |
| 179 | + |
| 180 | + stream.on('data', function (data) { |
| 181 | + data = (buff + data).split(/\n+/); |
| 182 | + var l = data.length - 1; |
| 183 | + |
| 184 | + for (var i = 0; i < l; i++) { |
| 185 | + if (!options.start || row >= options.start) { |
| 186 | + add(data[i]); |
| 187 | + } |
| 188 | + row++; |
| 189 | + } |
| 190 | + |
| 191 | + buff = data[l]; |
| 192 | + }); |
| 193 | + |
| 194 | + stream.on('close', function () { |
| 195 | + if (buff) { |
| 196 | + add(buff, true); |
| 197 | + } |
| 198 | + |
| 199 | + if (options.order === 'desc') { |
| 200 | + results = results.reverse(); |
| 201 | + } |
| 202 | + |
| 203 | + if (logFiles.length) { |
| 204 | + processLogFile(logFiles.shift()); |
| 205 | + } else if (callback) { |
| 206 | + callback(null, results); |
| 207 | + } |
| 208 | + }); |
| 209 | + |
| 210 | + function add(buff, attempt) { |
| 211 | + try { |
| 212 | + var log = JSON.parse(buff); |
| 213 | + if (check(log)) { |
| 214 | + push(log); |
| 215 | + } |
| 216 | + } catch (e) { |
| 217 | + if (!attempt) { |
| 218 | + stream.emit('error', e); |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + |
| 223 | + function check(log) { |
| 224 | + if (!log || typeof log !== 'object') { |
| 225 | + return; |
| 226 | + } |
| 227 | + |
| 228 | + var time = new Date(log.timestamp); |
| 229 | + if ((options.from && time < options.from) || (options.until && time > options.until)) { |
| 230 | + return; |
| 231 | + } |
| 232 | + |
| 233 | + return true; |
| 234 | + } |
| 235 | + |
| 236 | + function push(log) { |
| 237 | + if (options.rows && results.length >= options.rows && options.order !== 'desc') { |
| 238 | + if (stream.readable) { |
| 239 | + stream.destroy(); |
| 240 | + } |
| 241 | + return; |
| 242 | + } |
| 243 | + |
| 244 | + if (options.fields) { |
| 245 | + var obj = {}; |
| 246 | + options.fields.forEach(function (key) { |
| 247 | + obj[key] = log[key]; |
| 248 | + }); |
| 249 | + log = obj; |
| 250 | + } |
| 251 | + |
| 252 | + if (options.order === 'desc') { |
| 253 | + if (results.length >= options.rows) { |
| 254 | + results.shift(); |
| 255 | + } |
| 256 | + } |
| 257 | + results.push(log); |
| 258 | + } |
| 259 | + })(logFiles.shift()); |
| 260 | +}; |
0 commit comments