Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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"]);
26 /*
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);
32 }
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 }
203 };
205 /*
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();
232 }
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 }
248 };
250 /*
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;
263 }
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 }
411 };
413 /*
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 = {__proto__: log};
502 for (let level in Log.Level) {
503 if (level == "Desc") {
504 continue;
505 }
507 let lc = level.toLowerCase();
508 proxy[lc] = function (msg, ...args) {
509 return log[lc].apply(log, [prefix + msg, ...args]);
510 };
511 }
513 return proxy;
514 },
515 };
517 /*
518 * Formatters
519 * These massage a LogMessage into whatever output is desired.
520 * BasicFormatter and StructuredFormatter are implemented here.
521 */
523 // Abstract formatter
524 function Formatter() {}
525 Formatter.prototype = {
526 format: function Formatter_format(message) {}
527 };
529 // Basic formatter that doesn't do anything fancy.
530 function BasicFormatter(dateFormat) {
531 if (dateFormat) {
532 this.dateFormat = dateFormat;
533 }
534 this.parameterFormatter = new ParameterFormatter();
535 }
536 BasicFormatter.prototype = {
537 __proto__: Formatter.prototype,
539 /**
540 * Format the text of a message with optional parameters.
541 * If the text contains ${identifier}, replace that with
542 * the value of params[identifier]; if ${}, replace that with
543 * the entire params object. If no params have been substituted
544 * into the text, format the entire object and append that
545 * to the message.
546 */
547 formatText: function (message) {
548 let params = message.params;
549 if (!params) {
550 return message.message || "";
551 }
552 // Defensive handling of non-object params
553 // We could add a special case for NSRESULT values here...
554 let pIsObject = (typeof(params) == 'object' || typeof(params) == 'function');
556 // if we have params, try and find substitutions.
557 if (message.params && this.parameterFormatter) {
558 // have we successfully substituted any parameters into the message?
559 // in the log message
560 let subDone = false;
561 let regex = /\$\{(\S*)\}/g;
562 let textParts = [];
563 if (message.message) {
564 textParts.push(message.message.replace(regex, (_, sub) => {
565 // ${foo} means use the params['foo']
566 if (sub) {
567 if (pIsObject && sub in message.params) {
568 subDone = true;
569 return this.parameterFormatter.format(message.params[sub]);
570 }
571 return '${' + sub + '}';
572 }
573 // ${} means use the entire params object.
574 subDone = true;
575 return this.parameterFormatter.format(message.params);
576 }));
577 }
578 if (!subDone) {
579 // There were no substitutions in the text, so format the entire params object
580 let rest = this.parameterFormatter.format(message.params);
581 if (rest !== null && rest != "{}") {
582 textParts.push(rest);
583 }
584 }
585 return textParts.join(': ');
586 }
587 },
589 format: function BF_format(message) {
590 return message.time + "\t" +
591 message.loggerName + "\t" +
592 message.levelDesc + "\t" +
593 this.formatText(message);
594 }
595 };
597 /**
598 * A formatter that only formats the string message component.
599 */
600 function MessageOnlyFormatter() {
601 }
602 MessageOnlyFormatter.prototype = Object.freeze({
603 __proto__: Formatter.prototype,
605 format: function (message) {
606 return message.message;
607 },
608 });
610 // Structured formatter that outputs JSON based on message data.
611 // This formatter will format unstructured messages by supplying
612 // default values.
613 function StructuredFormatter() { }
614 StructuredFormatter.prototype = {
615 __proto__: Formatter.prototype,
617 format: function (logMessage) {
618 let output = {
619 _time: logMessage.time,
620 _namespace: logMessage.loggerName,
621 _level: logMessage.levelDesc
622 };
624 for (let key in logMessage.params) {
625 output[key] = logMessage.params[key];
626 }
628 if (!output.action) {
629 output.action = "UNKNOWN";
630 }
632 if (!output._message && logMessage.message) {
633 output._message = logMessage.message;
634 }
636 return JSON.stringify(output);
637 }
638 }
640 /**
641 * Test an object to see if it is a Mozilla JS Error.
642 */
643 function isError(aObj) {
644 return (aObj && typeof(aObj) == 'object' && "name" in aObj && "message" in aObj &&
645 "fileName" in aObj && "lineNumber" in aObj && "stack" in aObj);
646 };
648 /*
649 * Parameter Formatters
650 * These massage an object used as a parameter for a LogMessage into
651 * a string representation of the object.
652 */
654 function ParameterFormatter() {
655 this._name = "ParameterFormatter"
656 }
657 ParameterFormatter.prototype = {
658 format: function(ob) {
659 try {
660 if (ob === undefined) {
661 return "undefined";
662 }
663 if (ob === null) {
664 return "null";
665 }
666 // Pass through primitive types and objects that unbox to primitive types.
667 if ((typeof(ob) != "object" || typeof(ob.valueOf()) != "object") &&
668 typeof(ob) != "function") {
669 return ob;
670 }
671 if (ob instanceof Ci.nsIException) {
672 return ob.toString() + " " + Log.stackTrace(ob);
673 }
674 else if (isError(ob)) {
675 return Log._formatError(ob);
676 }
677 // Just JSONify it. Filter out our internal fields and those the caller has
678 // already handled.
679 return JSON.stringify(ob, (key, val) => {
680 if (INTERNAL_FIELDS.has(key)) {
681 return undefined;
682 }
683 return val;
684 });
685 }
686 catch (e) {
687 dumpError("Exception trying to format object for log message: " + Log.exceptionStr(e));
688 }
689 // Fancy formatting failed. Just toSource() it - but even this may fail!
690 try {
691 return ob.toSource();
692 } catch (_) { }
693 try {
694 return "" + ob;
695 } catch (_) {
696 return "[object]"
697 }
698 }
699 }
701 /*
702 * Appenders
703 * These can be attached to Loggers to log to different places
704 * Simply subclass and override doAppend to implement a new one
705 */
707 function Appender(formatter) {
708 this._name = "Appender";
709 this._formatter = formatter? formatter : new BasicFormatter();
710 }
711 Appender.prototype = {
712 level: Log.Level.All,
714 append: function App_append(message) {
715 if (message) {
716 this.doAppend(this._formatter.format(message));
717 }
718 },
719 toString: function App_toString() {
720 return this._name + " [level=" + this.level +
721 ", formatter=" + this._formatter + "]";
722 },
723 doAppend: function App_doAppend(formatted) {}
724 };
726 /*
727 * DumpAppender
728 * Logs to standard out
729 */
731 function DumpAppender(formatter) {
732 Appender.call(this, formatter);
733 this._name = "DumpAppender";
734 }
735 DumpAppender.prototype = {
736 __proto__: Appender.prototype,
738 doAppend: function DApp_doAppend(formatted) {
739 dump(formatted + "\n");
740 }
741 };
743 /*
744 * ConsoleAppender
745 * Logs to the javascript console
746 */
748 function ConsoleAppender(formatter) {
749 Appender.call(this, formatter);
750 this._name = "ConsoleAppender";
751 }
752 ConsoleAppender.prototype = {
753 __proto__: Appender.prototype,
755 // XXX this should be replaced with calls to the Browser Console
756 append: function App_append(message) {
757 if (message) {
758 let m = this._formatter.format(message);
759 if (message.level > Log.Level.Warn) {
760 Cu.reportError(m);
761 return;
762 }
763 this.doAppend(m);
764 }
765 },
767 doAppend: function CApp_doAppend(formatted) {
768 Cc["@mozilla.org/consoleservice;1"].
769 getService(Ci.nsIConsoleService).logStringMessage(formatted);
770 }
771 };
773 /**
774 * Append to an nsIStorageStream
775 *
776 * This writes logging output to an in-memory stream which can later be read
777 * back as an nsIInputStream. It can be used to avoid expensive I/O operations
778 * during logging. Instead, one can periodically consume the input stream and
779 * e.g. write it to disk asynchronously.
780 */
781 function StorageStreamAppender(formatter) {
782 Appender.call(this, formatter);
783 this._name = "StorageStreamAppender";
784 }
786 StorageStreamAppender.prototype = {
787 __proto__: Appender.prototype,
789 _converterStream: null, // holds the nsIConverterOutputStream
790 _outputStream: null, // holds the underlying nsIOutputStream
792 _ss: null,
794 get outputStream() {
795 if (!this._outputStream) {
796 // First create a raw stream. We can bail out early if that fails.
797 this._outputStream = this.newOutputStream();
798 if (!this._outputStream) {
799 return null;
800 }
802 // Wrap the raw stream in an nsIConverterOutputStream. We can reuse
803 // the instance if we already have one.
804 if (!this._converterStream) {
805 this._converterStream = Cc["@mozilla.org/intl/converter-output-stream;1"]
806 .createInstance(Ci.nsIConverterOutputStream);
807 }
808 this._converterStream.init(
809 this._outputStream, "UTF-8", STREAM_SEGMENT_SIZE,
810 Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
811 }
812 return this._converterStream;
813 },
815 newOutputStream: function newOutputStream() {
816 let ss = this._ss = Cc["@mozilla.org/storagestream;1"]
817 .createInstance(Ci.nsIStorageStream);
818 ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null);
819 return ss.getOutputStream(0);
820 },
822 getInputStream: function getInputStream() {
823 if (!this._ss) {
824 return null;
825 }
826 return this._ss.newInputStream(0);
827 },
829 reset: function reset() {
830 if (!this._outputStream) {
831 return;
832 }
833 this.outputStream.close();
834 this._outputStream = null;
835 this._ss = null;
836 },
838 doAppend: function (formatted) {
839 if (!formatted) {
840 return;
841 }
842 try {
843 this.outputStream.writeString(formatted + "\n");
844 } catch(ex) {
845 if (ex.result == Cr.NS_BASE_STREAM_CLOSED) {
846 // The underlying output stream is closed, so let's open a new one
847 // and try again.
848 this._outputStream = null;
849 } try {
850 this.outputStream.writeString(formatted + "\n");
851 } catch (ex) {
852 // Ah well, we tried, but something seems to be hosed permanently.
853 }
854 }
855 }
856 };
858 /**
859 * File appender
860 *
861 * Writes output to file using OS.File.
862 */
863 function FileAppender(path, formatter) {
864 Appender.call(this, formatter);
865 this._name = "FileAppender";
866 this._encoder = new TextEncoder();
867 this._path = path;
868 this._file = null;
869 this._fileReadyPromise = null;
871 // This is a promise exposed for testing/debugging the logger itself.
872 this._lastWritePromise = null;
873 }
875 FileAppender.prototype = {
876 __proto__: Appender.prototype,
878 _openFile: function () {
879 return Task.spawn(function _openFile() {
880 try {
881 this._file = yield OS.File.open(this._path,
882 {truncate: true});
883 } catch (err) {
884 if (err instanceof OS.File.Error) {
885 this._file = null;
886 } else {
887 throw err;
888 }
889 }
890 }.bind(this));
891 },
893 _getFile: function() {
894 if (!this._fileReadyPromise) {
895 this._fileReadyPromise = this._openFile();
896 return this._fileReadyPromise;
897 }
899 return this._fileReadyPromise.then(_ => {
900 if (!this._file) {
901 return this._openFile();
902 }
903 });
904 },
906 doAppend: function (formatted) {
907 let array = this._encoder.encode(formatted + "\n");
908 if (this._file) {
909 this._lastWritePromise = this._file.write(array);
910 } else {
911 this._lastWritePromise = this._getFile().then(_ => {
912 this._fileReadyPromise = null;
913 if (this._file) {
914 return this._file.write(array);
915 }
916 });
917 }
918 },
920 reset: function () {
921 let fileClosePromise = this._file.close();
922 return fileClosePromise.then(_ => {
923 this._file = null;
924 return OS.File.remove(this._path);
925 });
926 }
927 };
929 /**
930 * Bounded File appender
931 *
932 * Writes output to file using OS.File. After the total message size
933 * (as defined by formatted.length) exceeds maxSize, existing messages
934 * will be discarded, and subsequent writes will be appended to a new log file.
935 */
936 function BoundedFileAppender(path, formatter, maxSize=2*ONE_MEGABYTE) {
937 FileAppender.call(this, path, formatter);
938 this._name = "BoundedFileAppender";
939 this._size = 0;
940 this._maxSize = maxSize;
941 this._closeFilePromise = null;
942 }
944 BoundedFileAppender.prototype = {
945 __proto__: FileAppender.prototype,
947 doAppend: function (formatted) {
948 if (!this._removeFilePromise) {
949 if (this._size < this._maxSize) {
950 this._size += formatted.length;
951 return FileAppender.prototype.doAppend.call(this, formatted);
952 }
953 this._removeFilePromise = this.reset();
954 }
955 this._removeFilePromise.then(_ => {
956 this._removeFilePromise = null;
957 this.doAppend(formatted);
958 });
959 },
961 reset: function () {
962 let fileClosePromise;
963 if (this._fileReadyPromise) {
964 // An attempt to open the file may still be in progress.
965 fileClosePromise = this._fileReadyPromise.then(_ => {
966 return this._file.close();
967 });
968 } else {
969 fileClosePromise = this._file.close();
970 }
972 return fileClosePromise.then(_ => {
973 this._size = 0;
974 this._file = null;
975 return OS.File.remove(this._path);
976 });
977 }
978 };