1 var define = require("../../define.js").define, 2 promise = require("../../promise"), 3 Promise = promise.Promise, 4 PromiseList = promise.PromiseList, 5 base = require("../../base"), 6 hitch = base.hitch, 7 string = base.string, 8 escape = base.regexp.escapeString, 9 style = string.style, 10 format = string.format, 11 FileAppender = require("./fileAppender"), 12 Level = require("../level"), 13 fs = require("fs"), 14 path = require("path"); 15 16 var conversion = { 17 MB : 1048576, 18 KB : 1024, 19 GB : 1073741824 20 }; 21 var DEFAULT_SIZE = "10MB"; 22 var convertToBytes = function(str) { 23 var ret = DEFAULT_SIZE; 24 var match = str.match(/(\d+)(MB|KB|GB)$/); 25 if (match && match.length == 3) { 26 var size = parseInt(match[1], 10); 27 ret = size * conversion[match[2]]; 28 } 29 return ret; 30 } 31 32 /** 33 * @class Appends messages to a file. Rolls files over when a size limit has been reached. Once the max file size has 34 * been reached it is rolled over to a file called <logName>.log.n where n is a number. 35 * 36 * <p>Example. RollingFileAppender is current writing to myLog.log, the log reaches is max size to it is 37 * renamed to myLog.log.1 and a new myLog.log is created.</p> 38 * 39 * <p>If maxBackupIndex is reached then the log at that index is deleted. If maxBackupIndex is set to 0 then no log is 40 * rolled over.</p> 41 * 42 * @name RollingFileAppender 43 * @augments comb.logging.appenders.FileAppender 44 * @memberOf comb.logging.appenders 45 * 46 * @param {Object} [options] options to assign to this Appender 47 * @param {String} [options.name="appender"] the name of this Appender. If you want two of the same type of appender 48 * on a logger it must have a different name. 49 * @param {String} [options.pattern="[{[yyyy-MM-ddTHH:mm:ss:SSS (z)]timeStamp}] {[- 5]levelName} {[-20]name} - {message}"] 50 * <p>Available Options for formatting see {@link comb.string.format} for formatting options</p> 51 * <ul> 52 * <li>timeStamp - the timestamp of the event being logged</li> 53 * <li>level - the {@link comb.logging.Level} of the event</li> 54 * <li>levelName - the name of the level being logged</li> 55 * <li>name - the name of the logger logging the event</li> 56 * <li>message - the message being logged</li> 57 * </ul> 58 * @param {comb.logging.Level|String} [options.level=comb.logging.Level.INFO] the logging level of this appender 59 * <p><b>Note:</b> the level can be different from the logger in the case that you want a particular logger 60 * to only log particular event of a level. For example an appender that only logs errors. BEWARE that if the 61 * appenders level is lower than the logger is will not recieve any messages.</p> 62 * 63 * @param {String} [options.file="./log.log"] the file to log events to. 64 * @param {String} [options.encoding="utf8"] the encoding of the file. 65 * @param {Boolean} [options.overwrite=false] if true the log file is overwritten otherwise it is appended to. 66 * @param {String} [options.maxSize="10MB"] the maxSize of a file. Valid options include "KB", "MB", or "GB" 67 * 68 * <pre class="code"> 69 * maxSize = "100MB" 70 * //or 71 * maxSize = "100KB" 72 * //or 73 * maxSize = "1GB" 74 * </pre> 75 * 76 * @param {Number} [options.maxBackupIndex=10] the maximum number of files to rollOver. 77 */ 78 exports = module.exports = define(FileAppender, { 79 instance : { 80 81 constructor : function(options) { 82 options = options || {}; 83 this.maxSize = options.maxSize || DEFAULT_SIZE; 84 !options.name && (options.name = "rollingFileAppender"); 85 this.maxBackupIndex = options.maxBackupIndex || 10; 86 this.__queue = []; 87 this.__inRollover = false; 88 this.super(arguments, [options]); 89 fs.watchFile(this.__file, hitch(this, "__checkFile")); 90 fs.stat(this.__file, hitch(this, function(err, stat) { 91 this.__checkFile(stat); 92 })); 93 }, 94 95 __checkFile : function(stats) { 96 var ret = new Promise(); 97 if (!this.__inRollover) { 98 if (stats.size >= this.maxSize) { 99 if (this.maxBackupIndex > 0) { 100 this.__inRollover = true; 101 this.__onExit().chain(hitch(this, "__rollover")).then(hitch(this, function() { 102 var ws = fs.createWriteStream(this.__file, { flags: "w", encoding: this.__encoding}); 103 ws.on("open", hitch(this, function() { 104 this.__writeStream = ws; 105 this.__inRollover = false; 106 this.__checkQueue(); 107 ret.callback(); 108 })); 109 }), hitch(ret, "errback", new Error("comb.logging.appenders.RollingFileAppender : error rolling over files"))); 110 } else { 111 this.__writeStream = fs.createWriteStream(this.__file, { flags: "w", encoding: this.__encoding}); 112 ret.callback(); 113 } 114 } else { 115 ret.callback(); 116 } 117 } else { 118 ret.callback(); 119 } 120 return ret; 121 }, 122 123 124 append : function(event) { 125 if (this._canAppend(event)) { 126 var ws = this.__writeStream; 127 if (!this.__inRollover && ws && ws.writable) { 128 this.super(arguments); 129 } else { 130 this.__queue.push(event); 131 } 132 } 133 }, 134 135 __checkQueue : function() { 136 this.__queue.forEach(this.append, this); 137 this.__queue.length = 0; 138 }, 139 140 __rollover : function() { 141 var ret = new Promise(), file = this.__file; 142 var dir = path.dirname(file), baseName = new RegExp("(" + escape(path.basename(path.basename(file))) + ")(?:\\.(\\d*))*"); 143 fs.readdir(dir, hitch(this, function(err, files) { 144 files = files.filter( 145 function(f) { 146 var match = f.match(baseName); 147 if (match) { 148 return true; 149 } else { 150 return false; 151 } 152 }); 153 files = files.sort(function(a, b) { 154 var ret = 0; 155 if (a > b) { 156 ret = 0; 157 } else if (a < b) { 158 ret = 1; 159 } 160 return ret; 161 }); 162 var count = files.length, i = 0; 163 var checkFile = hitch(this, function() { 164 if (count > 0) { 165 var f = dir + "/" + files[i++]; 166 if (count > this.maxBackupIndex) { 167 //drop the file; 168 count--; 169 fs.unlink(f, function(err) { 170 err ? ret.errback(err) : checkFile(); 171 }); 172 } else { 173 //rename the file 174 var rn = this.__file + "." + count--; 175 fs.rename(f, rn, function(err) { 176 err ? ret.errback(err) : checkFile(); 177 }); 178 } 179 } else { 180 ret.callback(); 181 } 182 }); 183 checkFile(); 184 })); 185 return ret; 186 }, 187 188 189 getters : { 190 191 maxSize : function() { 192 return this.__maxSize; 193 } 194 }, 195 196 setters : { 197 maxSize : function(size) { 198 this.__maxSize = size ? convertToBytes(size) : DEFAULT_SIZE; 199 } 200 } 201 } 202 });