toolkit/modules/Log.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial