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         });