Bumping gaia.json for 1 gaia revision(s) a=gaia-bump
[gecko.git] / toolkit / modules / Log.jsm
blobc2c83bc7c6ecd2aa472d6f71d19bf644a440796d
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 this.EXPORTED_SYMBOLS = ["Log"];
9 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
11 const ONE_BYTE = 1;
12 const ONE_KILOBYTE = 1024 * ONE_BYTE;
13 const ONE_MEGABYTE = 1024 * ONE_KILOBYTE;
15 const STREAM_SEGMENT_SIZE = 4096;
16 const PR_UINT32_MAX = 0xffffffff;
18 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
19 XPCOMUtils.defineLazyModuleGetter(this, "OS",
20                                   "resource://gre/modules/osfile.jsm");
21 XPCOMUtils.defineLazyModuleGetter(this, "Task",
22                                   "resource://gre/modules/Task.jsm");
23 const INTERNAL_FIELDS = new Set(["_level", "_message", "_time", "_namespace"]);
27  * Dump a message everywhere we can if we have a failure.
28  */
29 function dumpError(text) {
30   dump(text + "\n");
31   Cu.reportError(text);
34 this.Log = {
35   Level: {
36     Fatal:  70,
37     Error:  60,
38     Warn:   50,
39     Info:   40,
40     Config: 30,
41     Debug:  20,
42     Trace:  10,
43     All:    0,
44     Desc: {
45       70: "FATAL",
46       60: "ERROR",
47       50: "WARN",
48       40: "INFO",
49       30: "CONFIG",
50       20: "DEBUG",
51       10: "TRACE",
52       0:  "ALL"
53     },
54     Numbers: {
55       "FATAL": 70,
56       "ERROR": 60,
57       "WARN": 50,
58       "INFO": 40,
59       "CONFIG": 30,
60       "DEBUG": 20,
61       "TRACE": 10,
62       "ALL": 0,
63     }
64   },
66   get repository() {
67     delete Log.repository;
68     Log.repository = new LoggerRepository();
69     return Log.repository;
70   },
71   set repository(value) {
72     delete Log.repository;
73     Log.repository = value;
74   },
76   LogMessage: LogMessage,
77   Logger: Logger,
78   LoggerRepository: LoggerRepository,
80   Formatter: Formatter,
81   BasicFormatter: BasicFormatter,
82   MessageOnlyFormatter: MessageOnlyFormatter,
83   StructuredFormatter: StructuredFormatter,
85   Appender: Appender,
86   DumpAppender: DumpAppender,
87   ConsoleAppender: ConsoleAppender,
88   StorageStreamAppender: StorageStreamAppender,
90   FileAppender: FileAppender,
91   BoundedFileAppender: BoundedFileAppender,
93   ParameterFormatter: ParameterFormatter,
94   // Logging helper:
95   // let logger = Log.repository.getLogger("foo");
96   // logger.info(Log.enumerateInterfaces(someObject).join(","));
97   enumerateInterfaces: function Log_enumerateInterfaces(aObject) {
98     let interfaces = [];
100     for (i in Ci) {
101       try {
102         aObject.QueryInterface(Ci[i]);
103         interfaces.push(i);
104       }
105       catch(ex) {}
106     }
108     return interfaces;
109   },
111   // Logging helper:
112   // let logger = Log.repository.getLogger("foo");
113   // logger.info(Log.enumerateProperties(someObject).join(","));
114   enumerateProperties: function (aObject, aExcludeComplexTypes) {
115     let properties = [];
117     for (p in aObject) {
118       try {
119         if (aExcludeComplexTypes &&
120             (typeof(aObject[p]) == "object" || typeof(aObject[p]) == "function"))
121           continue;
122         properties.push(p + " = " + aObject[p]);
123       }
124       catch(ex) {
125         properties.push(p + " = " + ex);
126       }
127     }
129     return properties;
130   },
132   _formatError: function _formatError(e) {
133     let result = e.toString();
134     if (e.fileName) {
135       result +=  " (" + e.fileName;
136       if (e.lineNumber) {
137         result += ":" + e.lineNumber;
138       }
139       if (e.columnNumber) {
140         result += ":" + e.columnNumber;
141       }
142       result += ")";
143     }
144     return result + " " + Log.stackTrace(e);
145   },
147   // This is for back compatibility with services/common/utils.js; we duplicate
148   // some of the logic in ParameterFormatter
149   exceptionStr: function exceptionStr(e) {
150     if (!e) {
151       return "" + e;
152     }
153     if (e instanceof Ci.nsIException) {
154       return e.toString() + " " + Log.stackTrace(e);
155     }
156     else if (isError(e)) {
157       return Log._formatError(e);
158     }
159     // else
160     let message = e.message ? e.message : e;
161     return message + " " + Log.stackTrace(e);
162   },
164   stackTrace: function stackTrace(e) {
165     // Wrapped nsIException
166     if (e.location) {
167       let frame = e.location;
168       let output = [];
169       while (frame) {
170         // Works on frames or exceptions, munges file:// URIs to shorten the paths
171         // FIXME: filename munging is sort of hackish, might be confusing if
172         // there are multiple extensions with similar filenames
173         let str = "<file:unknown>";
175         let file = frame.filename || frame.fileName;
176         if (file) {
177           str = file.replace(/^(?:chrome|file):.*?([^\/\.]+\.\w+)$/, "$1");
178         }
180         if (frame.lineNumber) {
181           str += ":" + frame.lineNumber;
182         }
184         if (frame.name) {
185           str = frame.name + "()@" + str;
186         }
188         if (str) {
189           output.push(str);
190         }
191         frame = frame.caller;
192       }
193       return "Stack trace: " + output.join(" < ");
194     }
195     // Standard JS exception
196     if (e.stack) {
197       return "JS Stack trace: " + e.stack.trim().replace(/\n/g, " < ").
198         replace(/@[^@]*?([^\/\.]+\.\w+:)/g, "@$1");
199     }
201     return "No traceback available";
202   }
206  * LogMessage
207  * Encapsulates a single log event's data
208  */
209 function LogMessage(loggerName, level, message, params) {
210   this.loggerName = loggerName;
211   this.level = level;
212   /*
213    * Special case to handle "log./level/(object)", for example logging a caught exception
214    * without providing text or params like: catch(e) { logger.warn(e) }
215    * Treating this as an empty text with the object in the 'params' field causes the
216    * object to be formatted properly by BasicFormatter.
217    */
218   if (!params && message && (typeof(message) == "object") &&
219       (typeof(message.valueOf()) != "string")) {
220     this.message = null;
221     this.params = message;
222   } else {
223     // If the message text is empty, or a string, or a String object, normal handling
224     this.message = message;
225     this.params = params;
226   }
228   // The _structured field will correspond to whether this message is to
229   // be interpreted as a structured message.
230   this._structured = this.params && this.params.action;
231   this.time = Date.now();
233 LogMessage.prototype = {
234   get levelDesc() {
235     if (this.level in Log.Level.Desc)
236       return Log.Level.Desc[this.level];
237     return "UNKNOWN";
238   },
240   toString: function LogMsg_toString() {
241     let msg = "LogMessage [" + this.time + " " + this.level + " " +
242       this.message;
243     if (this.params) {
244       msg += " " + JSON.stringify(this.params);
245     }
246     return msg + "]"
247   }
251  * Logger
252  * Hierarchical version.  Logs to all appenders, assigned or inherited
253  */
255 function Logger(name, repository) {
256   if (!repository)
257     repository = Log.repository;
258   this._name = name;
259   this.children = [];
260   this.ownAppenders = [];
261   this.appenders = [];
262   this._repository = repository;
264 Logger.prototype = {
265   get name() {
266     return this._name;
267   },
269   _level: null,
270   get level() {
271     if (this._level != null)
272       return this._level;
273     if (this.parent)
274       return this.parent.level;
275     dumpError("Log warning: root logger configuration error: no level defined");
276     return Log.Level.All;
277   },
278   set level(level) {
279     this._level = level;
280   },
282   _parent: null,
283   get parent() this._parent,
284   set parent(parent) {
285     if (this._parent == parent) {
286       return;
287     }
288     // Remove ourselves from parent's children
289     if (this._parent) {
290       let index = this._parent.children.indexOf(this);
291       if (index != -1) {
292         this._parent.children.splice(index, 1);
293       }
294     }
295     this._parent = parent;
296     parent.children.push(this);
297     this.updateAppenders();
298   },
300   updateAppenders: function updateAppenders() {
301     if (this._parent) {
302       let notOwnAppenders = this._parent.appenders.filter(function(appender) {
303         return this.ownAppenders.indexOf(appender) == -1;
304       }, this);
305       this.appenders = notOwnAppenders.concat(this.ownAppenders);
306     } else {
307       this.appenders = this.ownAppenders.slice();
308     }
310     // Update children's appenders.
311     for (let i = 0; i < this.children.length; i++) {
312       this.children[i].updateAppenders();
313     }
314   },
316   addAppender: function Logger_addAppender(appender) {
317     if (this.ownAppenders.indexOf(appender) != -1) {
318       return;
319     }
320     this.ownAppenders.push(appender);
321     this.updateAppenders();
322   },
324   removeAppender: function Logger_removeAppender(appender) {
325     let index = this.ownAppenders.indexOf(appender);
326     if (index == -1) {
327       return;
328     }
329     this.ownAppenders.splice(index, 1);
330     this.updateAppenders();
331   },
333   /**
334    * Logs a structured message object.
335    *
336    * @param action
337    *        (string) A message action, one of a set of actions known to the
338    *          log consumer.
339    * @param params
340    *        (object) Parameters to be included in the message.
341    *          If _level is included as a key and the corresponding value
342    *          is a number or known level name, the message will be logged
343    *          at the indicated level. If _message is included as a key, the
344    *          value is used as the descriptive text for the message.
345    */
346   logStructured: function (action, params) {
347     if (!action) {
348       throw "An action is required when logging a structured message.";
349     }
350     if (!params) {
351       return this.log(this.level, undefined, {"action": action});
352     }
353     if (typeof(params) != "object") {
354       throw "The params argument is required to be an object.";
355     }
357     let level = params._level;
358     if (level) {
359       let ulevel = level.toUpperCase();
360       if (ulevel in Log.Level.Numbers) {
361         level = Log.Level.Numbers[ulevel];
362       }
363     } else {
364       level = this.level;
365     }
367     params.action = action;
368     this.log(level, params._message, params);
369   },
371   log: function (level, string, params) {
372     if (this.level > level)
373       return;
375     // Hold off on creating the message object until we actually have
376     // an appender that's responsible.
377     let message;
378     let appenders = this.appenders;
379     for (let appender of appenders) {
380       if (appender.level > level) {
381         continue;
382       }
383       if (!message) {
384         message = new LogMessage(this._name, level, string, params);
385       }
386       appender.append(message);
387     }
388   },
390   fatal: function (string, params) {
391     this.log(Log.Level.Fatal, string, params);
392   },
393   error: function (string, params) {
394     this.log(Log.Level.Error, string, params);
395   },
396   warn: function (string, params) {
397     this.log(Log.Level.Warn, string, params);
398   },
399   info: function (string, params) {
400     this.log(Log.Level.Info, string, params);
401   },
402   config: function (string, params) {
403     this.log(Log.Level.Config, string, params);
404   },
405   debug: function (string, params) {
406     this.log(Log.Level.Debug, string, params);
407   },
408   trace: function (string, params) {
409     this.log(Log.Level.Trace, string, params);
410   }
414  * LoggerRepository
415  * Implements a hierarchy of Loggers
416  */
418 function LoggerRepository() {}
419 LoggerRepository.prototype = {
420   _loggers: {},
422   _rootLogger: null,
423   get rootLogger() {
424     if (!this._rootLogger) {
425       this._rootLogger = new Logger("root", this);
426       this._rootLogger.level = Log.Level.All;
427     }
428     return this._rootLogger;
429   },
430   set rootLogger(logger) {
431     throw "Cannot change the root logger";
432   },
434   _updateParents: function LogRep__updateParents(name) {
435     let pieces = name.split('.');
436     let cur, parent;
438     // find the closest parent
439     // don't test for the logger name itself, as there's a chance it's already
440     // there in this._loggers
441     for (let i = 0; i < pieces.length - 1; i++) {
442       if (cur)
443         cur += '.' + pieces[i];
444       else
445         cur = pieces[i];
446       if (cur in this._loggers)
447         parent = cur;
448     }
450     // if we didn't assign a parent above, there is no parent
451     if (!parent)
452       this._loggers[name].parent = this.rootLogger;
453     else
454       this._loggers[name].parent = this._loggers[parent];
456     // trigger updates for any possible descendants of this logger
457     for (let logger in this._loggers) {
458       if (logger != name && logger.indexOf(name) == 0)
459         this._updateParents(logger);
460     }
461   },
463   /**
464    * Obtain a named Logger.
465    *
466    * The returned Logger instance for a particular name is shared among
467    * all callers. In other words, if two consumers call getLogger("foo"),
468    * they will both have a reference to the same object.
469    *
470    * @return Logger
471    */
472   getLogger: function (name) {
473     if (name in this._loggers)
474       return this._loggers[name];
475     this._loggers[name] = new Logger(name, this);
476     this._updateParents(name);
477     return this._loggers[name];
478   },
480   /**
481    * Obtain a Logger that logs all string messages with a prefix.
482    *
483    * A common pattern is to have separate Logger instances for each instance
484    * of an object. But, you still want to distinguish between each instance.
485    * Since Log.repository.getLogger() returns shared Logger objects,
486    * monkeypatching one Logger modifies them all.
487    *
488    * This function returns a new object with a prototype chain that chains
489    * up to the original Logger instance. The new prototype has log functions
490    * that prefix content to each message.
491    *
492    * @param name
493    *        (string) The Logger to retrieve.
494    * @param prefix
495    *        (string) The string to prefix each logged message with.
496    */
497   getLoggerWithMessagePrefix: function (name, prefix) {
498     let log = this.getLogger(name);
500     let proxy = Object.create(log);
501     proxy.log = (level, string, params) => log.log(level, prefix + string, params);
502     return proxy;
503   },
507  * Formatters
508  * These massage a LogMessage into whatever output is desired.
509  * BasicFormatter and StructuredFormatter are implemented here.
510  */
512 // Abstract formatter
513 function Formatter() {}
514 Formatter.prototype = {
515   format: function Formatter_format(message) {}
518 // Basic formatter that doesn't do anything fancy.
519 function BasicFormatter(dateFormat) {
520   if (dateFormat) {
521     this.dateFormat = dateFormat;
522   }
523   this.parameterFormatter = new ParameterFormatter();
525 BasicFormatter.prototype = {
526   __proto__: Formatter.prototype,
528   /**
529    * Format the text of a message with optional parameters.
530    * If the text contains ${identifier}, replace that with
531    * the value of params[identifier]; if ${}, replace that with
532    * the entire params object. If no params have been substituted
533    * into the text, format the entire object and append that
534    * to the message.
535    */
536   formatText: function (message) {
537     let params = message.params;
538     if (!params) {
539       return message.message || "";
540     }
541     // Defensive handling of non-object params
542     // We could add a special case for NSRESULT values here...
543     let pIsObject = (typeof(params) == 'object' || typeof(params) == 'function');
545     // if we have params, try and find substitutions.
546     if (message.params && this.parameterFormatter) {
547       // have we successfully substituted any parameters into the message?
548       // in the log message
549       let subDone = false;
550       let regex = /\$\{(\S*)\}/g;
551       let textParts = [];
552       if (message.message) {
553         textParts.push(message.message.replace(regex, (_, sub) => {
554           // ${foo} means use the params['foo']
555           if (sub) {
556             if (pIsObject && sub in message.params) {
557               subDone = true;
558               return this.parameterFormatter.format(message.params[sub]);
559             }
560             return '${' + sub + '}';
561           }
562           // ${} means use the entire params object.
563           subDone = true;
564           return this.parameterFormatter.format(message.params);
565         }));
566       }
567       if (!subDone) {
568         // There were no substitutions in the text, so format the entire params object
569         let rest = this.parameterFormatter.format(message.params);
570         if (rest !== null && rest != "{}") {
571           textParts.push(rest);
572         }
573       }
574       return textParts.join(': ');
575     }
576   },
578   format: function BF_format(message) {
579     return message.time + "\t" +
580       message.loggerName + "\t" +
581       message.levelDesc + "\t" +
582       this.formatText(message);
583   }
587  * A formatter that only formats the string message component.
588  */
589 function MessageOnlyFormatter() {
591 MessageOnlyFormatter.prototype = Object.freeze({
592   __proto__: Formatter.prototype,
594   format: function (message) {
595     return message.message;
596   },
599 // Structured formatter that outputs JSON based on message data.
600 // This formatter will format unstructured messages by supplying
601 // default values.
602 function StructuredFormatter() { }
603 StructuredFormatter.prototype = {
604   __proto__: Formatter.prototype,
606   format: function (logMessage) {
607     let output = {
608       _time: logMessage.time,
609       _namespace: logMessage.loggerName,
610       _level: logMessage.levelDesc
611     };
613     for (let key in logMessage.params) {
614       output[key] = logMessage.params[key];
615     }
617     if (!output.action) {
618       output.action = "UNKNOWN";
619     }
621     if (!output._message && logMessage.message) {
622       output._message = logMessage.message;
623     }
625     return JSON.stringify(output);
626   }
630  * Test an object to see if it is a Mozilla JS Error.
631  */
632 function isError(aObj) {
633   return (aObj && typeof(aObj) == 'object' && "name" in aObj && "message" in aObj &&
634           "fileName" in aObj && "lineNumber" in aObj && "stack" in aObj);
638  * Parameter Formatters
639  * These massage an object used as a parameter for a LogMessage into
640  * a string representation of the object.
641  */
643 function ParameterFormatter() {
644   this._name = "ParameterFormatter"
646 ParameterFormatter.prototype = {
647   format: function(ob) {
648     try {
649       if (ob === undefined) {
650         return "undefined";
651       }
652       if (ob === null) {
653         return "null";
654       }
655       // Pass through primitive types and objects that unbox to primitive types.
656       if ((typeof(ob) != "object" || typeof(ob.valueOf()) != "object") &&
657           typeof(ob) != "function") {
658         return ob;
659       }
660       if (ob instanceof Ci.nsIException) {
661         return ob.toString() + " " + Log.stackTrace(ob);
662       }
663       else if (isError(ob)) {
664         return Log._formatError(ob);
665       }
666       // Just JSONify it. Filter out our internal fields and those the caller has
667       // already handled.
668       return JSON.stringify(ob, (key, val) => {
669         if (INTERNAL_FIELDS.has(key)) {
670           return undefined;
671         }
672         return val;
673       });
674     }
675     catch (e) {
676       dumpError("Exception trying to format object for log message: " + Log.exceptionStr(e));
677     }
678     // Fancy formatting failed. Just toSource() it - but even this may fail!
679     try {
680       return ob.toSource();
681     } catch (_) { }
682     try {
683       return "" + ob;
684     } catch (_) {
685       return "[object]"
686     }
687   }
691  * Appenders
692  * These can be attached to Loggers to log to different places
693  * Simply subclass and override doAppend to implement a new one
694  */
696 function Appender(formatter) {
697   this._name = "Appender";
698   this._formatter = formatter? formatter : new BasicFormatter();
700 Appender.prototype = {
701   level: Log.Level.All,
703   append: function App_append(message) {
704     if (message) {
705       this.doAppend(this._formatter.format(message));
706     }
707   },
708   toString: function App_toString() {
709     return this._name + " [level=" + this.level +
710       ", formatter=" + this._formatter + "]";
711   },
712   doAppend: function App_doAppend(formatted) {}
716  * DumpAppender
717  * Logs to standard out
718  */
720 function DumpAppender(formatter) {
721   Appender.call(this, formatter);
722   this._name = "DumpAppender";
724 DumpAppender.prototype = {
725   __proto__: Appender.prototype,
727   doAppend: function DApp_doAppend(formatted) {
728     dump(formatted + "\n");
729   }
733  * ConsoleAppender
734  * Logs to the javascript console
735  */
737 function ConsoleAppender(formatter) {
738   Appender.call(this, formatter);
739   this._name = "ConsoleAppender";
741 ConsoleAppender.prototype = {
742   __proto__: Appender.prototype,
744   // XXX this should be replaced with calls to the Browser Console
745   append: function App_append(message) {
746     if (message) {
747       let m = this._formatter.format(message);
748       if (message.level > Log.Level.Warn) {
749         Cu.reportError(m);
750         return;
751       }
752       this.doAppend(m);
753     }
754   },
756   doAppend: function CApp_doAppend(formatted) {
757     Cc["@mozilla.org/consoleservice;1"].
758       getService(Ci.nsIConsoleService).logStringMessage(formatted);
759   }
763  * Append to an nsIStorageStream
765  * This writes logging output to an in-memory stream which can later be read
766  * back as an nsIInputStream. It can be used to avoid expensive I/O operations
767  * during logging. Instead, one can periodically consume the input stream and
768  * e.g. write it to disk asynchronously.
769  */
770 function StorageStreamAppender(formatter) {
771   Appender.call(this, formatter);
772   this._name = "StorageStreamAppender";
775 StorageStreamAppender.prototype = {
776   __proto__: Appender.prototype,
778   _converterStream: null, // holds the nsIConverterOutputStream
779   _outputStream: null,    // holds the underlying nsIOutputStream
781   _ss: null,
783   get outputStream() {
784     if (!this._outputStream) {
785       // First create a raw stream. We can bail out early if that fails.
786       this._outputStream = this.newOutputStream();
787       if (!this._outputStream) {
788         return null;
789       }
791       // Wrap the raw stream in an nsIConverterOutputStream. We can reuse
792       // the instance if we already have one.
793       if (!this._converterStream) {
794         this._converterStream = Cc["@mozilla.org/intl/converter-output-stream;1"]
795                                   .createInstance(Ci.nsIConverterOutputStream);
796       }
797       this._converterStream.init(
798         this._outputStream, "UTF-8", STREAM_SEGMENT_SIZE,
799         Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
800     }
801     return this._converterStream;
802   },
804   newOutputStream: function newOutputStream() {
805     let ss = this._ss = Cc["@mozilla.org/storagestream;1"]
806                           .createInstance(Ci.nsIStorageStream);
807     ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null);
808     return ss.getOutputStream(0);
809   },
811   getInputStream: function getInputStream() {
812     if (!this._ss) {
813       return null;
814     }
815     return this._ss.newInputStream(0);
816   },
818   reset: function reset() {
819     if (!this._outputStream) {
820       return;
821     }
822     this.outputStream.close();
823     this._outputStream = null;
824     this._ss = null;
825   },
827   doAppend: function (formatted) {
828     if (!formatted) {
829       return;
830     }
831     try {
832       this.outputStream.writeString(formatted + "\n");
833     } catch(ex) {
834       if (ex.result == Cr.NS_BASE_STREAM_CLOSED) {
835         // The underlying output stream is closed, so let's open a new one
836         // and try again.
837         this._outputStream = null;
838       } try {
839           this.outputStream.writeString(formatted + "\n");
840       } catch (ex) {
841         // Ah well, we tried, but something seems to be hosed permanently.
842       }
843     }
844   }
848  * File appender
850  * Writes output to file using OS.File.
851  */
852 function FileAppender(path, formatter) {
853   Appender.call(this, formatter);
854   this._name = "FileAppender";
855   this._encoder = new TextEncoder();
856   this._path = path;
857   this._file = null;
858   this._fileReadyPromise = null;
860   // This is a promise exposed for testing/debugging the logger itself.
861   this._lastWritePromise = null;
864 FileAppender.prototype = {
865   __proto__: Appender.prototype,
867   _openFile: function () {
868     return Task.spawn(function _openFile() {
869       try {
870         this._file = yield OS.File.open(this._path,
871                                         {truncate: true});
872       } catch (err) {
873         if (err instanceof OS.File.Error) {
874           this._file = null;
875         } else {
876           throw err;
877         }
878       }
879     }.bind(this));
880   },
882   _getFile: function() {
883     if (!this._fileReadyPromise) {
884       this._fileReadyPromise = this._openFile();
885     }
887     return this._fileReadyPromise;
888   },
890   doAppend: function (formatted) {
891     let array = this._encoder.encode(formatted + "\n");
892     if (this._file) {
893       this._lastWritePromise = this._file.write(array);
894     } else {
895       this._lastWritePromise = this._getFile().then(_ => {
896         this._fileReadyPromise = null;
897         if (this._file) {
898           return this._file.write(array);
899         }
900       });
901     }
902   },
904   reset: function () {
905     let fileClosePromise = this._file.close();
906     return fileClosePromise.then(_ => {
907       this._file = null;
908       return OS.File.remove(this._path);
909     });
910   }
914  * Bounded File appender
916  * Writes output to file using OS.File. After the total message size
917  * (as defined by formatted.length) exceeds maxSize, existing messages
918  * will be discarded, and subsequent writes will be appended to a new log file.
919  */
920 function BoundedFileAppender(path, formatter, maxSize=2*ONE_MEGABYTE) {
921   FileAppender.call(this, path, formatter);
922   this._name = "BoundedFileAppender";
923   this._size = 0;
924   this._maxSize = maxSize;
925   this._closeFilePromise = null;
928 BoundedFileAppender.prototype = {
929   __proto__: FileAppender.prototype,
931   doAppend: function (formatted) {
932     if (!this._removeFilePromise) {
933       if (this._size < this._maxSize) {
934         this._size += formatted.length;
935         return FileAppender.prototype.doAppend.call(this, formatted);
936       }
937       this._removeFilePromise = this.reset();
938     }
939     this._removeFilePromise.then(_ => {
940       this._removeFilePromise = null;
941       this.doAppend(formatted);
942     });
943   },
945   reset: function () {
946     let fileClosePromise;
947     if (this._fileReadyPromise) {
948       // An attempt to open the file may still be in progress.
949       fileClosePromise = this._fileReadyPromise.then(_ => {
950         return this._file.close();
951       });
952     } else {
953       fileClosePromise = this._file.close();
954     }
956     return fileClosePromise.then(_ => {
957       this._size = 0;
958       this._file = null;
959       return OS.File.remove(this._path);
960     });
961   }