StructuredLog.jsm 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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. "use strict";
  5. this.EXPORTED_SYMBOLS = [
  6. "StructuredLogger",
  7. "StructuredFormatter"
  8. ];
  9. /**
  10. * TestLogger: Logger class generating messages compliant with the
  11. * structured logging protocol for tests exposed by mozlog
  12. *
  13. * @param name
  14. * The name of the logger to instantiate.
  15. * @param dumpFun
  16. * An underlying function to be used to log raw messages. This function
  17. * will receive the complete serialized json string to log.
  18. * @param mutators
  19. * An array of functions used to add global context to log messages.
  20. * These will each be called with the complete object to log as an
  21. * argument.
  22. */
  23. this.StructuredLogger = function(name, dumpFun=dump, mutators=[]) {
  24. this.name = name;
  25. this._dumpFun = dumpFun;
  26. this._mutatorFuns = mutators;
  27. }
  28. /**
  29. * Log functions producing messages in the format specified by mozlog
  30. */
  31. StructuredLogger.prototype = {
  32. testStart: function (test) {
  33. var data = {test: this._testId(test)};
  34. this._logData("test_start", data);
  35. },
  36. testStatus: function (test, subtest, status, expected="PASS",
  37. message=null, stack=null, extra=null) {
  38. if (subtest === null || subtest === undefined) {
  39. // Fix for assertions that don't pass in a name
  40. subtest = "undefined assertion name";
  41. }
  42. var data = {
  43. test: this._testId(test),
  44. subtest: subtest,
  45. status: status,
  46. };
  47. if (expected != status && status != "SKIP") {
  48. data.expected = expected;
  49. }
  50. if (message !== null) {
  51. data.message = String(message);
  52. }
  53. if (stack !== null) {
  54. data.stack = stack;
  55. }
  56. if (extra !== null) {
  57. data.extra = extra;
  58. }
  59. this._logData("test_status", data);
  60. },
  61. testEnd: function (test, status, expected="OK", message=null, stack=null, extra=null) {
  62. var data = {test: this._testId(test), status: status};
  63. if (expected != status && status != "SKIP") {
  64. data.expected = expected;
  65. }
  66. if (message !== null) {
  67. data.message = String(message);
  68. }
  69. if (stack !== null) {
  70. data.stack = stack;
  71. }
  72. if (extra !== null) {
  73. data.extra = extra;
  74. }
  75. this._logData("test_end", data);
  76. },
  77. suiteStart: function (tests, runinfo=null, versioninfo=null, deviceinfo=null, extra=null) {
  78. var data = {tests: tests.map(x => this._testId(x))};
  79. if (runinfo !== null) {
  80. data.runinfo = runinfo;
  81. }
  82. if (versioninfo !== null) {
  83. data.versioninfo = versioninfo;
  84. }
  85. if (deviceinfo !== null) {
  86. data.deviceinfo = deviceinfo;
  87. }
  88. if (extra !== null) {
  89. data.extra = extra;
  90. }
  91. this._logData("suite_start", data);
  92. },
  93. suiteEnd: function (extra=null) {
  94. var data = {};
  95. if (extra !== null) {
  96. data.extra = extra;
  97. }
  98. this._logData("suite_end", data);
  99. },
  100. /**
  101. * Unstructured logging functions. The "extra" parameter can always by used to
  102. * log suite specific data. If a "stack" field is provided it is logged at the
  103. * top level of the data object for the benefit of mozlog's formatters.
  104. */
  105. log: function (level, message, extra=null) {
  106. var data = {
  107. level: level,
  108. message: String(message),
  109. };
  110. if (extra !== null) {
  111. data.extra = extra;
  112. if ("stack" in extra) {
  113. data.stack = extra.stack;
  114. }
  115. }
  116. this._logData("log", data);
  117. },
  118. debug: function (message, extra=null) {
  119. this.log("DEBUG", message, extra);
  120. },
  121. info: function (message, extra=null) {
  122. this.log("INFO", message, extra);
  123. },
  124. warning: function (message, extra=null) {
  125. this.log("WARNING", message, extra);
  126. },
  127. error: function (message, extra=null) {
  128. this.log("ERROR", message, extra);
  129. },
  130. critical: function (message, extra=null) {
  131. this.log("CRITICAL", message, extra);
  132. },
  133. processOutput: function(thread, message) {
  134. this._logData('process_output', {
  135. message: message,
  136. thread: thread,
  137. });
  138. },
  139. _logData: function (action, data={}) {
  140. var allData = {
  141. action: action,
  142. time: Date.now(),
  143. thread: null,
  144. pid: null,
  145. source: this.name
  146. };
  147. for (var field in data) {
  148. allData[field] = data[field];
  149. }
  150. for (var fun of this._mutatorFuns) {
  151. fun(allData);
  152. }
  153. this._dumpFun(allData);
  154. },
  155. _testId: function(test) {
  156. if (Array.isArray(test)) {
  157. return test.join(" ");
  158. }
  159. return test;
  160. },
  161. };
  162. /**
  163. * StructuredFormatter: Formatter class turning structured messages
  164. * into human-readable messages.
  165. */
  166. this.StructuredFormatter = function() {
  167. this.testStartTimes = {};
  168. };
  169. StructuredFormatter.prototype = {
  170. log: function(message) {
  171. return message.message;
  172. },
  173. suite_start: function(message) {
  174. this.suiteStartTime = message.time;
  175. return "SUITE-START | Running " + message.tests.length + " tests";
  176. },
  177. test_start: function(message) {
  178. this.testStartTimes[message.test] = new Date().getTime();
  179. return "TEST-START | " + message.test;
  180. },
  181. test_status: function(message) {
  182. var statusInfo = message.test + " | " + message.subtest +
  183. (message.message ? " | " + message.message : "");
  184. if (message.expected) {
  185. return "TEST-UNEXPECTED-" + message.status + " | " + statusInfo +
  186. " - expected: " + message.expected;
  187. } else {
  188. return "TEST-" + message.status + " | " + statusInfo;
  189. }
  190. },
  191. test_end: function(message) {
  192. var startTime = this.testStartTimes[message.test];
  193. delete this.testStartTimes[message.test];
  194. var statusInfo = message.test + (message.message ? " | " + String(message.message) : "");
  195. var result;
  196. if (message.expected) {
  197. result = "TEST-UNEXPECTED-" + message.status + " | " + statusInfo +
  198. " - expected: " + message.expected;
  199. } else {
  200. return "TEST-" + message.status + " | " + statusInfo;
  201. }
  202. result = " | took " + message.time - startTime + "ms";
  203. },
  204. suite_end: function(message) {
  205. return "SUITE-END | took " + message.time - this.suiteStartTime + "ms";
  206. }
  207. };