console.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  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 file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. define(["util"], function (util) {
  5. var console = window.console || {log: function () {}};
  6. var Console = util.Class({
  7. constructor: function () {
  8. this.messages = [];
  9. this.level = this.levels.log;
  10. },
  11. messageLimit: 100,
  12. levels: {
  13. debug: 1,
  14. // FIXME: I'm considering *not* wrapping console.log, and strictly keeping
  15. // it as a debugging tool; also line numbers would be preserved
  16. log: 2,
  17. info: 3,
  18. notify: 4,
  19. warn: 5,
  20. error: 6,
  21. fatal: 7
  22. },
  23. // Gets set below:
  24. maxLevel: 0,
  25. consoleLevels: [
  26. [],
  27. console.debug || [],
  28. console.log || [],
  29. console.info || [],
  30. console.notify || [],
  31. console.warn || [],
  32. console.error || [],
  33. console.fatal || []
  34. ],
  35. levelNames: {},
  36. setLevel: function (l) {
  37. var number;
  38. if (typeof l == "string") {
  39. number = this.levels[l];
  40. if (number === undefined) {
  41. throw new Error("Tried to set Console level to unknown level string: " + l);
  42. }
  43. l = number;
  44. }
  45. if (typeof l == "function") {
  46. number = this.consoleLevels.indexOf(l);
  47. if (number == -1) {
  48. throw new Error("Tried to set Console level based on unknown console function: " + l);
  49. }
  50. l = number;
  51. }
  52. if (typeof l == "number") {
  53. if (l < 0) {
  54. throw new Error("Console level must be 0 or larger: " + l);
  55. } else if (l > this.maxLevel) {
  56. throw new Error("Console level must be " + this.maxLevel + " or smaller: " + l);
  57. }
  58. }
  59. this.level = l;
  60. },
  61. write: function (level) {
  62. try {
  63. this.messages.push([
  64. Date.now(),
  65. level,
  66. this._stringify(Array.prototype.slice.call(arguments, 1))
  67. ]);
  68. } catch (e) {
  69. console.warn("Error stringifying argument:", e);
  70. }
  71. if (level != "suppress" && this.level <= level) {
  72. var method = console[this.levelNames[level]];
  73. if (! method) {
  74. method = console.log;
  75. }
  76. method.apply(console, Array.prototype.slice.call(arguments, 1));
  77. }
  78. },
  79. suppressedWrite: function () {
  80. this.write.apply(this, ["suppress"].concat(Array.prototype.slice.call(arguments)));
  81. },
  82. trace: function (level) {
  83. level = level || 'log';
  84. if (console.trace) {
  85. level = "suppressedWrite";
  86. }
  87. try {
  88. throw new Error();
  89. } catch (e) {
  90. // FIXME: trim this frame
  91. var stack = e.stack;
  92. stack = stack.replace(/^[^\n]*\n/, "");
  93. this[level](stack);
  94. }
  95. if (console.trace) {
  96. console.trace();
  97. }
  98. },
  99. _browserInfo: function () {
  100. // FIXME: add TogetherJS version and
  101. return [
  102. "TogetherJS base URL: " + TogetherJS.baseUrl,
  103. "User Agent: " + navigator.userAgent,
  104. "Page loaded: " + this._formatDate(TogetherJS.pageLoaded),
  105. "Age: " + this._formatMinutes(Date.now() - TogetherJS.pageLoaded) + " minutes",
  106. // FIXME: make this right:
  107. //"Window: height: " + window.screen.height + " width: " + window.screen.width
  108. "URL: " + location.href,
  109. "------+------+----------------------------------------------"
  110. ];
  111. },
  112. _stringify: function (args) {
  113. var s = "";
  114. for (var i=0; i<args.length; i++) {
  115. if (s) {
  116. s += " ";
  117. }
  118. s += this._stringifyItem(args[i]);
  119. }
  120. return s;
  121. },
  122. _stringifyItem: function (item) {
  123. if (typeof item == "string") {
  124. if (item === "") {
  125. return '""';
  126. }
  127. return item;
  128. }
  129. if (typeof item == "object" && item.repr) {
  130. try {
  131. return item.repr();
  132. } catch (e) {
  133. console.warn("Error getting object repr:", item, e);
  134. }
  135. }
  136. if (item !== null && typeof item == "object") {
  137. // FIXME: this can drop lots of kinds of values, like a function or undefined
  138. item = JSON.stringify(item);
  139. }
  140. return item.toString();
  141. },
  142. _formatDate: function (timestamp) {
  143. return (new Date(timestamp)).toISOString();
  144. },
  145. _formatTime: function (timestamp) {
  146. return ((timestamp - TogetherJS.pageLoaded) / 1000).toFixed(2);
  147. },
  148. _formatMinutes: function (milliseconds) {
  149. var m = Math.floor(milliseconds / 1000 / 60);
  150. var remaining = milliseconds - (m * 1000 * 60);
  151. if (m > 10) {
  152. // Over 10 minutes, just ignore the seconds
  153. return m;
  154. }
  155. var seconds = Math.floor(remaining / 1000) + "";
  156. m += ":";
  157. seconds = lpad(seconds, 2, "0");
  158. m += seconds;
  159. if (m == "0:00") {
  160. m += ((remaining / 1000).toFixed(3) + "").substr(1);
  161. }
  162. return m;
  163. },
  164. _formatLevel: function (l) {
  165. if (l === "suppress") {
  166. return "";
  167. }
  168. return this.levelNames[l];
  169. },
  170. toString: function () {
  171. try {
  172. var lines = this._browserInfo();
  173. this.messages.forEach(function (m) {
  174. lines.push(lpad(this._formatTime(m[0]), 6) + " " + rpad(this._formatLevel(m[1]), 6) + " " + lpadLines(m[2], 14));
  175. }, this);
  176. return lines.join("\n");
  177. } catch (e) {
  178. // toString errors can otherwise be swallowed:
  179. console.warn("Error running console.toString():", e);
  180. throw e;
  181. }
  182. },
  183. submit: function (options) {
  184. // FIXME: friendpaste is broken for this
  185. // (and other pastebin sites aren't really Browser-accessible)
  186. return util.Deferred(function (def) {
  187. options = options || {};
  188. var site = options.site || TogetherJS.config.get("pasteSite") || "https://www.friendpaste.com/";
  189. var req = new XMLHttpRequest();
  190. req.open("POST", site);
  191. req.setRequestHeader("Content-Type", "application/json");
  192. req.send(JSON.stringify({
  193. "title": options.title || "TogetherJS log file",
  194. "snippet": this.toString(),
  195. "language": "text"
  196. }));
  197. req.onreadystatechange = function () {
  198. if (req.readyState === 4) {
  199. var data = JSON.parse(req.responseText);
  200. }
  201. };
  202. });
  203. }
  204. });
  205. function rpad(s, len, pad) {
  206. s = s + "";
  207. pad = pad || " ";
  208. while (s.length < len) {
  209. s += pad;
  210. }
  211. return s;
  212. }
  213. function lpad(s, len, pad) {
  214. s = s + "";
  215. pad = pad || " ";
  216. while (s.length < len) {
  217. s = pad + s;
  218. }
  219. return s;
  220. }
  221. function lpadLines(s, len, pad) {
  222. var i;
  223. s = s + "";
  224. if (s.indexOf("\n") == -1) {
  225. return s;
  226. }
  227. pad = pad || " ";
  228. var fullPad = "";
  229. for (i=0; i<len; i++) {
  230. fullPad += pad;
  231. }
  232. s = s.split(/\n/g);
  233. for (i=1; i<s.length; i++) {
  234. s[i] = fullPad + s[i];
  235. }
  236. return s.join("\n");
  237. }
  238. // This is a factory that creates `Console.prototype.debug`, `.error` etc:
  239. function logFunction(name, level) {
  240. return function () {
  241. this.write.apply(this, [level].concat(Array.prototype.slice.call(arguments)));
  242. };
  243. }
  244. util.forEachAttr(Console.prototype.levels, function (value, name) {
  245. Console.prototype[name] = logFunction(name, value);
  246. Console.prototype.maxLevel = Math.max(Console.prototype.maxLevel, value);
  247. });
  248. util.forEachAttr(Console.prototype.levels, function (value, name) {
  249. Console.prototype.levelNames[value] = name;
  250. });
  251. var appConsole = Console();
  252. appConsole.ConsoleClass = Console;
  253. return appConsole;
  254. });