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