forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			746 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			746 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 | |
| 
 | |
| const INTERNAL_FIELDS = new Set(["_level", "_message", "_time", "_namespace"]);
 | |
| 
 | |
| /*
 | |
|  * Dump a message everywhere we can if we have a failure.
 | |
|  */
 | |
| function dumpError(text) {
 | |
|   dump(text + "\n");
 | |
|   // TODO: Bug 1801091 - Figure out how to replace this.
 | |
|   // eslint-disable-next-line mozilla/no-cu-reportError
 | |
|   Cu.reportError(text);
 | |
| }
 | |
| 
 | |
| export var Log = {
 | |
|   Level: {
 | |
|     Fatal: 70,
 | |
|     Error: 60,
 | |
|     Warn: 50,
 | |
|     Info: 40,
 | |
|     Config: 30,
 | |
|     Debug: 20,
 | |
|     Trace: 10,
 | |
|     All: -1, // We don't want All to be falsy.
 | |
|     Desc: {
 | |
|       70: "FATAL",
 | |
|       60: "ERROR",
 | |
|       50: "WARN",
 | |
|       40: "INFO",
 | |
|       30: "CONFIG",
 | |
|       20: "DEBUG",
 | |
|       10: "TRACE",
 | |
|       "-1": "ALL",
 | |
|     },
 | |
|     Numbers: {
 | |
|       FATAL: 70,
 | |
|       ERROR: 60,
 | |
|       WARN: 50,
 | |
|       INFO: 40,
 | |
|       CONFIG: 30,
 | |
|       DEBUG: 20,
 | |
|       TRACE: 10,
 | |
|       ALL: -1,
 | |
|     },
 | |
|   },
 | |
| 
 | |
|   get repository() {
 | |
|     delete Log.repository;
 | |
|     Log.repository = new LoggerRepository();
 | |
|     return Log.repository;
 | |
|   },
 | |
|   set repository(value) {
 | |
|     delete Log.repository;
 | |
|     Log.repository = value;
 | |
|   },
 | |
| 
 | |
|   _formatError(e) {
 | |
|     let result = String(e);
 | |
|     if (e.fileName) {
 | |
|       let loc = [e.fileName];
 | |
|       if (e.lineNumber) {
 | |
|         loc.push(e.lineNumber);
 | |
|       }
 | |
|       if (e.columnNumber) {
 | |
|         loc.push(e.columnNumber);
 | |
|       }
 | |
|       result += `(${loc.join(":")})`;
 | |
|     }
 | |
|     return `${result} ${Log.stackTrace(e)}`;
 | |
|   },
 | |
| 
 | |
|   // This is for back compatibility with services/common/utils.js; we duplicate
 | |
|   // some of the logic in ParameterFormatter
 | |
|   exceptionStr(e) {
 | |
|     if (!e) {
 | |
|       return String(e);
 | |
|     }
 | |
|     if (e instanceof Ci.nsIException) {
 | |
|       return `${e} ${Log.stackTrace(e)}`;
 | |
|     } else if (isError(e)) {
 | |
|       return Log._formatError(e);
 | |
|     }
 | |
|     // else
 | |
|     let message = e.message || e;
 | |
|     return `${message} ${Log.stackTrace(e)}`;
 | |
|   },
 | |
| 
 | |
|   stackTrace(e) {
 | |
|     if (!e) {
 | |
|       return Components.stack.caller.formattedStack.trim();
 | |
|     }
 | |
|     // Wrapped nsIException
 | |
|     if (e.location) {
 | |
|       let frame = e.location;
 | |
|       let output = [];
 | |
|       while (frame) {
 | |
|         // Works on frames or exceptions, munges file:// URIs to shorten the paths
 | |
|         // FIXME: filename munging is sort of hackish.
 | |
|         let str = "<file:unknown>";
 | |
| 
 | |
|         let file = frame.filename || frame.fileName;
 | |
|         if (file) {
 | |
|           str = file.replace(/^(?:chrome|file):.*?([^\/\.]+(\.\w+)+)$/, "$1");
 | |
|         }
 | |
| 
 | |
|         if (frame.lineNumber) {
 | |
|           str += ":" + frame.lineNumber;
 | |
|         }
 | |
| 
 | |
|         if (frame.name) {
 | |
|           str = frame.name + "()@" + str;
 | |
|         }
 | |
| 
 | |
|         if (str) {
 | |
|           output.push(str);
 | |
|         }
 | |
|         frame = frame.caller;
 | |
|       }
 | |
|       return `Stack trace: ${output.join("\n")}`;
 | |
|     }
 | |
|     // Standard JS exception
 | |
|     if (e.stack) {
 | |
|       let stack = e.stack;
 | |
|       return (
 | |
|         "JS Stack trace: " +
 | |
|         stack.trim().replace(/@[^@]*?([^\/\.]+(\.\w+)+:)/g, "@$1")
 | |
|       );
 | |
|     }
 | |
| 
 | |
|     if (e instanceof Ci.nsIStackFrame) {
 | |
|       return e.formattedStack.trim();
 | |
|     }
 | |
|     return "No traceback available";
 | |
|   },
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * LogMessage
 | |
|  * Encapsulates a single log event's data
 | |
|  */
 | |
| class LogMessage {
 | |
|   constructor(loggerName, level, message, params) {
 | |
|     this.loggerName = loggerName;
 | |
|     this.level = level;
 | |
|     /*
 | |
|      * Special case to handle "log./level/(object)", for example logging a caught exception
 | |
|      * without providing text or params like: catch(e) { logger.warn(e) }
 | |
|      * Treating this as an empty text with the object in the 'params' field causes the
 | |
|      * object to be formatted properly by BasicFormatter.
 | |
|      */
 | |
|     if (
 | |
|       !params &&
 | |
|       message &&
 | |
|       typeof message == "object" &&
 | |
|       typeof message.valueOf() != "string"
 | |
|     ) {
 | |
|       this.message = null;
 | |
|       this.params = message;
 | |
|     } else {
 | |
|       // If the message text is empty, or a string, or a String object, normal handling
 | |
|       this.message = message;
 | |
|       this.params = params;
 | |
|     }
 | |
| 
 | |
|     // The _structured field will correspond to whether this message is to
 | |
|     // be interpreted as a structured message.
 | |
|     this._structured = this.params && this.params.action;
 | |
|     this.time = Date.now();
 | |
|   }
 | |
| 
 | |
|   get levelDesc() {
 | |
|     if (this.level in Log.Level.Desc) {
 | |
|       return Log.Level.Desc[this.level];
 | |
|     }
 | |
|     return "UNKNOWN";
 | |
|   }
 | |
| 
 | |
|   toString() {
 | |
|     let msg = `${this.time} ${this.level} ${this.message}`;
 | |
|     if (this.params) {
 | |
|       msg += ` ${JSON.stringify(this.params)}`;
 | |
|     }
 | |
|     return `LogMessage [${msg}]`;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Logger
 | |
|  * Hierarchical version.  Logs to all appenders, assigned or inherited
 | |
|  */
 | |
| 
 | |
| class Logger {
 | |
|   constructor(name, repository) {
 | |
|     if (!repository) {
 | |
|       repository = Log.repository;
 | |
|     }
 | |
|     this._name = name;
 | |
|     this.children = [];
 | |
|     this.ownAppenders = [];
 | |
|     this.appenders = [];
 | |
|     this._repository = repository;
 | |
| 
 | |
|     this._levelPrefName = null;
 | |
|     this._levelPrefValue = null;
 | |
|     this._level = null;
 | |
|     this._parent = null;
 | |
|   }
 | |
| 
 | |
|   get name() {
 | |
|     return this._name;
 | |
|   }
 | |
| 
 | |
|   get level() {
 | |
|     if (this._levelPrefName) {
 | |
|       // We've been asked to use a preference to configure the logs. If the
 | |
|       // pref has a value we use it, otherwise we continue to use the parent.
 | |
|       const lpv = this._levelPrefValue;
 | |
|       if (lpv) {
 | |
|         const levelValue = Log.Level[lpv];
 | |
|         if (levelValue) {
 | |
|           // stash it in _level just in case a future value of the pref is
 | |
|           // invalid, in which case we end up continuing to use this value.
 | |
|           this._level = levelValue;
 | |
|           return levelValue;
 | |
|         }
 | |
|       } else {
 | |
|         // in case the pref has transitioned from a value to no value, we reset
 | |
|         // this._level and fall through to using the parent.
 | |
|         this._level = null;
 | |
|       }
 | |
|     }
 | |
|     if (this._level != null) {
 | |
|       return this._level;
 | |
|     }
 | |
|     if (this.parent) {
 | |
|       return this.parent.level;
 | |
|     }
 | |
|     dumpError("Log warning: root logger configuration error: no level defined");
 | |
|     return Log.Level.All;
 | |
|   }
 | |
|   set level(level) {
 | |
|     if (this._levelPrefName) {
 | |
|       // I guess we could honor this by nuking this._levelPrefValue, but it
 | |
|       // almost certainly implies confusion, so we'll warn and ignore.
 | |
|       dumpError(
 | |
|         `Log warning: The log '${this.name}' is configured to use ` +
 | |
|           `the preference '${this._levelPrefName}' - you must adjust ` +
 | |
|           `the level by setting this preference, not by using the ` +
 | |
|           `level setter`
 | |
|       );
 | |
|       return;
 | |
|     }
 | |
|     this._level = level;
 | |
|   }
 | |
| 
 | |
|   get parent() {
 | |
|     return this._parent;
 | |
|   }
 | |
|   set parent(parent) {
 | |
|     if (this._parent == parent) {
 | |
|       return;
 | |
|     }
 | |
|     // Remove ourselves from parent's children
 | |
|     if (this._parent) {
 | |
|       let index = this._parent.children.indexOf(this);
 | |
|       if (index != -1) {
 | |
|         this._parent.children.splice(index, 1);
 | |
|       }
 | |
|     }
 | |
|     this._parent = parent;
 | |
|     parent.children.push(this);
 | |
|     this.updateAppenders();
 | |
|   }
 | |
| 
 | |
|   manageLevelFromPref(prefName) {
 | |
|     if (prefName == this._levelPrefName) {
 | |
|       // We've already configured this log with an observer for that pref.
 | |
|       return;
 | |
|     }
 | |
|     if (this._levelPrefName) {
 | |
|       dumpError(
 | |
|         `The log '${this.name}' is already configured with the ` +
 | |
|           `preference '${this._levelPrefName}' - ignoring request to ` +
 | |
|           `also use the preference '${prefName}'`
 | |
|       );
 | |
|       return;
 | |
|     }
 | |
|     this._levelPrefName = prefName;
 | |
|     XPCOMUtils.defineLazyPreferenceGetter(this, "_levelPrefValue", prefName);
 | |
|   }
 | |
| 
 | |
|   updateAppenders() {
 | |
|     if (this._parent) {
 | |
|       let notOwnAppenders = this._parent.appenders.filter(function(appender) {
 | |
|         return !this.ownAppenders.includes(appender);
 | |
|       }, this);
 | |
|       this.appenders = notOwnAppenders.concat(this.ownAppenders);
 | |
|     } else {
 | |
|       this.appenders = this.ownAppenders.slice();
 | |
|     }
 | |
| 
 | |
|     // Update children's appenders.
 | |
|     for (let i = 0; i < this.children.length; i++) {
 | |
|       this.children[i].updateAppenders();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   addAppender(appender) {
 | |
|     if (this.ownAppenders.includes(appender)) {
 | |
|       return;
 | |
|     }
 | |
|     this.ownAppenders.push(appender);
 | |
|     this.updateAppenders();
 | |
|   }
 | |
| 
 | |
|   removeAppender(appender) {
 | |
|     let index = this.ownAppenders.indexOf(appender);
 | |
|     if (index == -1) {
 | |
|       return;
 | |
|     }
 | |
|     this.ownAppenders.splice(index, 1);
 | |
|     this.updateAppenders();
 | |
|   }
 | |
| 
 | |
|   _unpackTemplateLiteral(string, params) {
 | |
|     if (!Array.isArray(params)) {
 | |
|       // Regular log() call.
 | |
|       return [string, params];
 | |
|     }
 | |
| 
 | |
|     if (!Array.isArray(string)) {
 | |
|       // Not using template literal. However params was packed into an array by
 | |
|       // the this.[level] call, so we need to unpack it here.
 | |
|       return [string, params[0]];
 | |
|     }
 | |
| 
 | |
|     // We're using template literal format (logger.warn `foo ${bar}`). Turn the
 | |
|     // template strings into one string containing "${0}"..."${n}" tokens, and
 | |
|     // feed it to the basic formatter. The formatter will treat the numbers as
 | |
|     // indices into the params array, and convert the tokens to the params.
 | |
| 
 | |
|     if (!params.length) {
 | |
|       // No params; we need to set params to undefined, so the formatter
 | |
|       // doesn't try to output the params array.
 | |
|       return [string[0], undefined];
 | |
|     }
 | |
| 
 | |
|     let concat = string[0];
 | |
|     for (let i = 0; i < params.length; i++) {
 | |
|       concat += `\${${i}}${string[i + 1]}`;
 | |
|     }
 | |
|     return [concat, params];
 | |
|   }
 | |
| 
 | |
|   log(level, string, params) {
 | |
|     if (this.level > level) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Hold off on creating the message object until we actually have
 | |
|     // an appender that's responsible.
 | |
|     let message;
 | |
|     let appenders = this.appenders;
 | |
|     for (let appender of appenders) {
 | |
|       if (appender.level > level) {
 | |
|         continue;
 | |
|       }
 | |
|       if (!message) {
 | |
|         [string, params] = this._unpackTemplateLiteral(string, params);
 | |
|         message = new LogMessage(this._name, level, string, params);
 | |
|       }
 | |
|       appender.append(message);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   fatal(string, ...params) {
 | |
|     this.log(Log.Level.Fatal, string, params);
 | |
|   }
 | |
|   error(string, ...params) {
 | |
|     this.log(Log.Level.Error, string, params);
 | |
|   }
 | |
|   warn(string, ...params) {
 | |
|     this.log(Log.Level.Warn, string, params);
 | |
|   }
 | |
|   info(string, ...params) {
 | |
|     this.log(Log.Level.Info, string, params);
 | |
|   }
 | |
|   config(string, ...params) {
 | |
|     this.log(Log.Level.Config, string, params);
 | |
|   }
 | |
|   debug(string, ...params) {
 | |
|     this.log(Log.Level.Debug, string, params);
 | |
|   }
 | |
|   trace(string, ...params) {
 | |
|     this.log(Log.Level.Trace, string, params);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * LoggerRepository
 | |
|  * Implements a hierarchy of Loggers
 | |
|  */
 | |
| 
 | |
| class LoggerRepository {
 | |
|   constructor() {
 | |
|     this._loggers = {};
 | |
|     this._rootLogger = null;
 | |
|   }
 | |
| 
 | |
|   get rootLogger() {
 | |
|     if (!this._rootLogger) {
 | |
|       this._rootLogger = new Logger("root", this);
 | |
|       this._rootLogger.level = Log.Level.All;
 | |
|     }
 | |
|     return this._rootLogger;
 | |
|   }
 | |
|   set rootLogger(logger) {
 | |
|     throw new Error("Cannot change the root logger");
 | |
|   }
 | |
| 
 | |
|   _updateParents(name) {
 | |
|     let pieces = name.split(".");
 | |
|     let cur, parent;
 | |
| 
 | |
|     // find the closest parent
 | |
|     // don't test for the logger name itself, as there's a chance it's already
 | |
|     // there in this._loggers
 | |
|     for (let i = 0; i < pieces.length - 1; i++) {
 | |
|       if (cur) {
 | |
|         cur += "." + pieces[i];
 | |
|       } else {
 | |
|         cur = pieces[i];
 | |
|       }
 | |
|       if (cur in this._loggers) {
 | |
|         parent = cur;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // if we didn't assign a parent above, there is no parent
 | |
|     if (!parent) {
 | |
|       this._loggers[name].parent = this.rootLogger;
 | |
|     } else {
 | |
|       this._loggers[name].parent = this._loggers[parent];
 | |
|     }
 | |
| 
 | |
|     // trigger updates for any possible descendants of this logger
 | |
|     for (let logger in this._loggers) {
 | |
|       if (logger != name && logger.indexOf(name) == 0) {
 | |
|         this._updateParents(logger);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Obtain a named Logger.
 | |
|    *
 | |
|    * The returned Logger instance for a particular name is shared among
 | |
|    * all callers. In other words, if two consumers call getLogger("foo"),
 | |
|    * they will both have a reference to the same object.
 | |
|    *
 | |
|    * @return Logger
 | |
|    */
 | |
|   getLogger(name) {
 | |
|     if (name in this._loggers) {
 | |
|       return this._loggers[name];
 | |
|     }
 | |
|     this._loggers[name] = new Logger(name, this);
 | |
|     this._updateParents(name);
 | |
|     return this._loggers[name];
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Obtain a Logger that logs all string messages with a prefix.
 | |
|    *
 | |
|    * A common pattern is to have separate Logger instances for each instance
 | |
|    * of an object. But, you still want to distinguish between each instance.
 | |
|    * Since Log.repository.getLogger() returns shared Logger objects,
 | |
|    * monkeypatching one Logger modifies them all.
 | |
|    *
 | |
|    * This function returns a new object with a prototype chain that chains
 | |
|    * up to the original Logger instance. The new prototype has log functions
 | |
|    * that prefix content to each message.
 | |
|    *
 | |
|    * @param name
 | |
|    *        (string) The Logger to retrieve.
 | |
|    * @param prefix
 | |
|    *        (string) The string to prefix each logged message with.
 | |
|    */
 | |
|   getLoggerWithMessagePrefix(name, prefix) {
 | |
|     let log = this.getLogger(name);
 | |
| 
 | |
|     let proxy = Object.create(log);
 | |
|     proxy.log = (level, string, params) => {
 | |
|       if (Array.isArray(string) && Array.isArray(params)) {
 | |
|         // Template literal.
 | |
|         // We cannot change the original array, so create a new one.
 | |
|         string = [prefix + string[0]].concat(string.slice(1));
 | |
|       } else {
 | |
|         string = prefix + string; // Regular string.
 | |
|       }
 | |
|       return log.log(level, string, params);
 | |
|     };
 | |
|     return proxy;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Formatters
 | |
|  * These massage a LogMessage into whatever output is desired.
 | |
|  */
 | |
| 
 | |
| // Basic formatter that doesn't do anything fancy.
 | |
| class BasicFormatter {
 | |
|   constructor(dateFormat) {
 | |
|     if (dateFormat) {
 | |
|       this.dateFormat = dateFormat;
 | |
|     }
 | |
|     this.parameterFormatter = new ParameterFormatter();
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Format the text of a message with optional parameters.
 | |
|    * If the text contains ${identifier}, replace that with
 | |
|    * the value of params[identifier]; if ${}, replace that with
 | |
|    * the entire params object. If no params have been substituted
 | |
|    * into the text, format the entire object and append that
 | |
|    * to the message.
 | |
|    */
 | |
|   formatText(message) {
 | |
|     let params = message.params;
 | |
|     if (typeof params == "undefined") {
 | |
|       return message.message || "";
 | |
|     }
 | |
|     // Defensive handling of non-object params
 | |
|     // We could add a special case for NSRESULT values here...
 | |
|     let pIsObject = typeof params == "object" || typeof params == "function";
 | |
| 
 | |
|     // if we have params, try and find substitutions.
 | |
|     if (this.parameterFormatter) {
 | |
|       // have we successfully substituted any parameters into the message?
 | |
|       // in the log message
 | |
|       let subDone = false;
 | |
|       let regex = /\$\{(\S*?)\}/g;
 | |
|       let textParts = [];
 | |
|       if (message.message) {
 | |
|         textParts.push(
 | |
|           message.message.replace(regex, (_, sub) => {
 | |
|             // ${foo} means use the params['foo']
 | |
|             if (sub) {
 | |
|               if (pIsObject && sub in message.params) {
 | |
|                 subDone = true;
 | |
|                 return this.parameterFormatter.format(message.params[sub]);
 | |
|               }
 | |
|               return "${" + sub + "}";
 | |
|             }
 | |
|             // ${} means use the entire params object.
 | |
|             subDone = true;
 | |
|             return this.parameterFormatter.format(message.params);
 | |
|           })
 | |
|         );
 | |
|       }
 | |
|       if (!subDone) {
 | |
|         // There were no substitutions in the text, so format the entire params object
 | |
|         let rest = this.parameterFormatter.format(message.params);
 | |
|         if (rest !== null && rest != "{}") {
 | |
|           textParts.push(rest);
 | |
|         }
 | |
|       }
 | |
|       return textParts.join(": ");
 | |
|     }
 | |
|     return undefined;
 | |
|   }
 | |
| 
 | |
|   format(message) {
 | |
|     return (
 | |
|       message.time +
 | |
|       "\t" +
 | |
|       message.loggerName +
 | |
|       "\t" +
 | |
|       message.levelDesc +
 | |
|       "\t" +
 | |
|       this.formatText(message)
 | |
|     );
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Test an object to see if it is a Mozilla JS Error.
 | |
|  */
 | |
| function isError(aObj) {
 | |
|   return (
 | |
|     aObj &&
 | |
|     typeof aObj == "object" &&
 | |
|     "name" in aObj &&
 | |
|     "message" in aObj &&
 | |
|     "fileName" in aObj &&
 | |
|     "lineNumber" in aObj &&
 | |
|     "stack" in aObj
 | |
|   );
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Parameter Formatters
 | |
|  * These massage an object used as a parameter for a LogMessage into
 | |
|  * a string representation of the object.
 | |
|  */
 | |
| 
 | |
| class ParameterFormatter {
 | |
|   constructor() {
 | |
|     this._name = "ParameterFormatter";
 | |
|   }
 | |
| 
 | |
|   format(ob) {
 | |
|     try {
 | |
|       if (ob === undefined) {
 | |
|         return "undefined";
 | |
|       }
 | |
|       if (ob === null) {
 | |
|         return "null";
 | |
|       }
 | |
|       // Pass through primitive types and objects that unbox to primitive types.
 | |
|       if (
 | |
|         (typeof ob != "object" || typeof ob.valueOf() != "object") &&
 | |
|         typeof ob != "function"
 | |
|       ) {
 | |
|         return ob;
 | |
|       }
 | |
|       if (ob instanceof Ci.nsIException) {
 | |
|         return `${ob} ${Log.stackTrace(ob)}`;
 | |
|       } else if (isError(ob)) {
 | |
|         return Log._formatError(ob);
 | |
|       }
 | |
|       // Just JSONify it. Filter out our internal fields and those the caller has
 | |
|       // already handled.
 | |
|       return JSON.stringify(ob, (key, val) => {
 | |
|         if (INTERNAL_FIELDS.has(key)) {
 | |
|           return undefined;
 | |
|         }
 | |
|         return val;
 | |
|       });
 | |
|     } catch (e) {
 | |
|       dumpError(
 | |
|         `Exception trying to format object for log message: ${Log.exceptionStr(
 | |
|           e
 | |
|         )}`
 | |
|       );
 | |
|     }
 | |
|     // Fancy formatting failed. Just toSource() it - but even this may fail!
 | |
|     try {
 | |
|       return ob.toSource();
 | |
|     } catch (_) {}
 | |
|     try {
 | |
|       return String(ob);
 | |
|     } catch (_) {
 | |
|       return "[object]";
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Appenders
 | |
|  * These can be attached to Loggers to log to different places
 | |
|  * Simply subclass and override doAppend to implement a new one
 | |
|  */
 | |
| 
 | |
| class Appender {
 | |
|   constructor(formatter) {
 | |
|     this.level = Log.Level.All;
 | |
|     this._name = "Appender";
 | |
|     this._formatter = formatter || new BasicFormatter();
 | |
|   }
 | |
| 
 | |
|   append(message) {
 | |
|     if (message) {
 | |
|       this.doAppend(this._formatter.format(message));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   toString() {
 | |
|     return `${this._name} [level=${this.level}, formatter=${this._formatter}]`;
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * DumpAppender
 | |
|  * Logs to standard out
 | |
|  */
 | |
| 
 | |
| class DumpAppender extends Appender {
 | |
|   constructor(formatter) {
 | |
|     super(formatter);
 | |
|     this._name = "DumpAppender";
 | |
|   }
 | |
| 
 | |
|   doAppend(formatted) {
 | |
|     dump(formatted + "\n");
 | |
|   }
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * ConsoleAppender
 | |
|  * Logs to the javascript console
 | |
|  */
 | |
| 
 | |
| class ConsoleAppender extends Appender {
 | |
|   constructor(formatter) {
 | |
|     super(formatter);
 | |
|     this._name = "ConsoleAppender";
 | |
|   }
 | |
| 
 | |
|   // XXX this should be replaced with calls to the Browser Console
 | |
|   append(message) {
 | |
|     if (message) {
 | |
|       let m = this._formatter.format(message);
 | |
|       if (message.level > Log.Level.Warn) {
 | |
|         // TODO: Bug 1801091 - Figure out how to replace this.
 | |
|         // eslint-disable-next-line mozilla/no-cu-reportError
 | |
|         Cu.reportError(m);
 | |
|         return;
 | |
|       }
 | |
|       this.doAppend(m);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   doAppend(formatted) {
 | |
|     Services.console.logStringMessage(formatted);
 | |
|   }
 | |
| }
 | |
| 
 | |
| Object.assign(Log, {
 | |
|   LogMessage,
 | |
|   Logger,
 | |
|   LoggerRepository,
 | |
| 
 | |
|   BasicFormatter,
 | |
| 
 | |
|   Appender,
 | |
|   DumpAppender,
 | |
|   ConsoleAppender,
 | |
| 
 | |
|   ParameterFormatter,
 | |
| });
 | 
