tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

StructuredLog.sys.mjs (6875B)


      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/. */
      4 
      5 /**
      6 * TestLogger: Logger class generating messages compliant with the
      7 * structured logging protocol for tests exposed by mozlog
      8 *
      9 * @param {string} name
     10 *        The name of the logger to instantiate.
     11 * @param {function} [dumpFun]
     12 *        An underlying function to be used to log raw messages. This function
     13 *        will receive the complete serialized json string to log.
     14 * @param {object} [scope]
     15 *        The scope that the dumpFun is loaded in, so that messages are cloned
     16 *        into that scope before passing them.
     17 */
     18 export class StructuredLogger {
     19  name = null;
     20  #dumpFun = null;
     21  #dumpScope = null;
     22 
     23  constructor(name, dumpFun = dump, scope = null) {
     24    this.name = name;
     25    this.#dumpFun = dumpFun;
     26    this.#dumpScope = scope;
     27  }
     28 
     29  testStart(test) {
     30    var data = { test: this.#testId(test) };
     31    this.logData("test_start", data);
     32  }
     33 
     34  testStatus(
     35    test,
     36    subtest,
     37    status,
     38    expected = "PASS",
     39    message = null,
     40    stack = null,
     41    extra = null
     42  ) {
     43    var data = {
     44      test: this.#testId(test),
     45      subtest,
     46      status,
     47    };
     48 
     49    // handle case: known fail
     50    if (expected === status && status != "SKIP") {
     51      data.status = "PASS";
     52    }
     53    if (expected != status && status != "SKIP") {
     54      data.expected = expected;
     55    }
     56    if (message !== null) {
     57      data.message = String(message);
     58    }
     59    if (stack !== null) {
     60      data.stack = stack;
     61    }
     62    if (extra !== null) {
     63      data.extra = extra;
     64    }
     65 
     66    this.logData("test_status", data);
     67  }
     68 
     69  testEnd(
     70    test,
     71    status,
     72    expected = "OK",
     73    message = null,
     74    stack = null,
     75    extra = null
     76  ) {
     77    var data = { test: this.#testId(test), status };
     78 
     79    // handle case: known fail
     80    if (expected === status && status != "SKIP") {
     81      data.status = "OK";
     82    }
     83    if (expected != status && status != "SKIP") {
     84      data.expected = expected;
     85    }
     86    if (message !== null) {
     87      data.message = String(message);
     88    }
     89    if (stack !== null) {
     90      data.stack = stack;
     91    }
     92    if (extra !== null) {
     93      data.extra = extra;
     94    }
     95 
     96    this.logData("test_end", data);
     97  }
     98 
     99  assertionCount(test, count, minExpected = 0, maxExpected = 0) {
    100    var data = {
    101      test: this.#testId(test),
    102      min_expected: minExpected,
    103      max_expected: maxExpected,
    104      count,
    105    };
    106 
    107    this.logData("assertion_count", data);
    108  }
    109 
    110  suiteStart(
    111    ids,
    112    name = null,
    113    runinfo = null,
    114    versioninfo = null,
    115    deviceinfo = null,
    116    extra = null
    117  ) {
    118    Object.keys(ids).map(function (manifest) {
    119      ids[manifest] = ids[manifest].map(x => this.#testId(x));
    120    }, this);
    121    var data = { tests: ids };
    122 
    123    if (name !== null) {
    124      data.name = name;
    125    }
    126 
    127    if (runinfo !== null) {
    128      data.runinfo = runinfo;
    129    }
    130 
    131    if (versioninfo !== null) {
    132      data.versioninfo = versioninfo;
    133    }
    134 
    135    if (deviceinfo !== null) {
    136      data.deviceinfo = deviceinfo;
    137    }
    138 
    139    if (extra !== null) {
    140      data.extra = extra;
    141    }
    142 
    143    this.logData("suite_start", data);
    144  }
    145 
    146  suiteEnd(extra = null) {
    147    var data = {};
    148 
    149    if (extra !== null) {
    150      data.extra = extra;
    151    }
    152 
    153    this.logData("suite_end", data);
    154  }
    155 
    156  /**
    157   * Unstructured logging functions. The "extra" parameter can always by used to
    158   * log suite specific data. If a "stack" field is provided it is logged at the
    159   * top level of the data object for the benefit of mozlog's formatters.
    160   */
    161  log(level, message, extra = null) {
    162    var data = {
    163      level,
    164      message: String(message),
    165    };
    166 
    167    if (extra !== null) {
    168      data.extra = extra;
    169      if ("stack" in extra) {
    170        data.stack = extra.stack;
    171      }
    172    }
    173 
    174    this.logData("log", data);
    175  }
    176 
    177  debug(message, extra = null) {
    178    this.log("DEBUG", message, extra);
    179  }
    180 
    181  info(message, extra = null) {
    182    this.log("INFO", message, extra);
    183  }
    184 
    185  warning(message, extra = null) {
    186    this.log("WARNING", message, extra);
    187  }
    188 
    189  error(message, extra = null) {
    190    this.log("ERROR", message, extra);
    191  }
    192 
    193  critical(message, extra = null) {
    194    this.log("CRITICAL", message, extra);
    195  }
    196 
    197  processOutput(thread, message) {
    198    this.logData("process_output", {
    199      message,
    200      thread,
    201    });
    202  }
    203 
    204  logData(action, data = {}) {
    205    var allData = {
    206      action,
    207      time: Date.now(),
    208      thread: null,
    209      pid: null,
    210      source: this.name,
    211    };
    212 
    213    for (var field in data) {
    214      allData[field] = data[field];
    215    }
    216 
    217    if (this.#dumpScope) {
    218      try {
    219        allData = Cu.cloneInto(allData, this.#dumpScope);
    220      } catch (e) {
    221        try {
    222          this.error(`Failed to cloneInto: ${e}`);
    223          this.warning(`Tried to clone: ${uneval(allData)}`);
    224        } catch (e2) {
    225          console.error("Failed to handle clone error", e, e2);
    226        }
    227      }
    228    }
    229    this.#dumpFun(allData);
    230  }
    231 
    232  #testId(test) {
    233    if (Array.isArray(test)) {
    234      return test.join(" ");
    235    }
    236    return test;
    237  }
    238 }
    239 
    240 /**
    241 * StructuredFormatter: Formatter class turning structured messages
    242 * into human-readable messages.
    243 */
    244 export class StructuredFormatter {
    245  // The time at which the whole suite of tests started.
    246  #suiteStartTime = null;
    247 
    248  #testStartTimes = new Map();
    249 
    250  log(message) {
    251    return message.message;
    252  }
    253 
    254  suite_start(message) {
    255    this.#suiteStartTime = message.time;
    256    return "SUITE-START | Running " + message.tests.length + " tests";
    257  }
    258 
    259  test_start(message) {
    260    this.#testStartTimes.set(message.test, new Date().getTime());
    261    return "TEST-START | " + message.test;
    262  }
    263 
    264  test_status(message) {
    265    var statusInfo =
    266      message.test +
    267      " | " +
    268      message.subtest +
    269      (message.message ? " | " + message.message : "");
    270    if (message.expected) {
    271      return (
    272        "TEST-UNEXPECTED-" +
    273        message.status +
    274        " | " +
    275        statusInfo +
    276        " - expected: " +
    277        message.expected
    278      );
    279    }
    280    return "TEST-" + message.status + " | " + statusInfo;
    281  }
    282 
    283  test_end(message) {
    284    var startTime = this.#testStartTimes.get(message.test);
    285    this.#testStartTimes.delete(message.test);
    286    var statusInfo =
    287      message.test + (message.message ? " | " + String(message.message) : "");
    288    var result;
    289    if (message.expected) {
    290      result =
    291        "TEST-UNEXPECTED-" +
    292        message.status +
    293        " | " +
    294        statusInfo +
    295        " - expected: " +
    296        message.expected;
    297    } else {
    298      return "TEST-" + message.status + " | " + statusInfo;
    299    }
    300    result = result + " | took " + message.time - startTime + "ms";
    301    return result;
    302  }
    303 
    304  suite_end(message) {
    305    return "SUITE-END | took " + message.time - this.#suiteStartTime + "ms";
    306  }
    307 }