formatter.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. // Copyright 2006 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Definition of various formatters for logging. Please minimize
  16. * dependencies this file has on other closure classes as any dependency it
  17. * takes won't be able to use the logging infrastructure.
  18. *
  19. */
  20. goog.provide('goog.debug.Formatter');
  21. goog.provide('goog.debug.HtmlFormatter');
  22. goog.provide('goog.debug.TextFormatter');
  23. goog.require('goog.debug');
  24. goog.require('goog.debug.Logger');
  25. goog.require('goog.debug.RelativeTimeProvider');
  26. goog.require('goog.html.SafeHtml');
  27. goog.require('goog.html.SafeUrl');
  28. goog.require('goog.html.uncheckedconversions');
  29. goog.require('goog.string.Const');
  30. /**
  31. * Base class for Formatters. A Formatter is used to format a LogRecord into
  32. * something that can be displayed to the user.
  33. *
  34. * @param {string=} opt_prefix The prefix to place before text records.
  35. * @constructor
  36. */
  37. goog.debug.Formatter = function(opt_prefix) {
  38. this.prefix_ = opt_prefix || '';
  39. /**
  40. * A provider that returns the relative start time.
  41. * @type {goog.debug.RelativeTimeProvider}
  42. * @private
  43. */
  44. this.startTimeProvider_ =
  45. goog.debug.RelativeTimeProvider.getDefaultInstance();
  46. };
  47. /**
  48. * Whether to append newlines to the end of formatted log records.
  49. * @type {boolean}
  50. */
  51. goog.debug.Formatter.prototype.appendNewline = true;
  52. /**
  53. * Whether to show absolute time in the DebugWindow.
  54. * @type {boolean}
  55. */
  56. goog.debug.Formatter.prototype.showAbsoluteTime = true;
  57. /**
  58. * Whether to show relative time in the DebugWindow.
  59. * @type {boolean}
  60. */
  61. goog.debug.Formatter.prototype.showRelativeTime = true;
  62. /**
  63. * Whether to show the logger name in the DebugWindow.
  64. * @type {boolean}
  65. */
  66. goog.debug.Formatter.prototype.showLoggerName = true;
  67. /**
  68. * Whether to show the logger exception text.
  69. * @type {boolean}
  70. */
  71. goog.debug.Formatter.prototype.showExceptionText = false;
  72. /**
  73. * Whether to show the severity level.
  74. * @type {boolean}
  75. */
  76. goog.debug.Formatter.prototype.showSeverityLevel = false;
  77. /**
  78. * Formats a record.
  79. * @param {goog.debug.LogRecord} logRecord the logRecord to format.
  80. * @return {string} The formatted string.
  81. */
  82. goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;
  83. /**
  84. * Formats a record as SafeHtml.
  85. * @param {goog.debug.LogRecord} logRecord the logRecord to format.
  86. * @return {!goog.html.SafeHtml} The formatted string as SafeHtml.
  87. */
  88. goog.debug.Formatter.prototype.formatRecordAsHtml = goog.abstractMethod;
  89. /**
  90. * Sets the start time provider. By default, this is the default instance
  91. * but can be changed.
  92. * @param {goog.debug.RelativeTimeProvider} provider The provider to use.
  93. */
  94. goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {
  95. this.startTimeProvider_ = provider;
  96. };
  97. /**
  98. * Returns the start time provider. By default, this is the default instance
  99. * but can be changed.
  100. * @return {goog.debug.RelativeTimeProvider} The start time provider.
  101. */
  102. goog.debug.Formatter.prototype.getStartTimeProvider = function() {
  103. return this.startTimeProvider_;
  104. };
  105. /**
  106. * Resets the start relative time.
  107. */
  108. goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {
  109. this.startTimeProvider_.reset();
  110. };
  111. /**
  112. * Returns a string for the time/date of the LogRecord.
  113. * @param {goog.debug.LogRecord} logRecord The record to get a time stamp for.
  114. * @return {string} A string representation of the time/date of the LogRecord.
  115. * @private
  116. */
  117. goog.debug.Formatter.getDateTimeStamp_ = function(logRecord) {
  118. var time = new Date(logRecord.getMillis());
  119. return goog.debug.Formatter.getTwoDigitString_((time.getFullYear() - 2000)) +
  120. goog.debug.Formatter.getTwoDigitString_((time.getMonth() + 1)) +
  121. goog.debug.Formatter.getTwoDigitString_(time.getDate()) + ' ' +
  122. goog.debug.Formatter.getTwoDigitString_(time.getHours()) + ':' +
  123. goog.debug.Formatter.getTwoDigitString_(time.getMinutes()) + ':' +
  124. goog.debug.Formatter.getTwoDigitString_(time.getSeconds()) + '.' +
  125. goog.debug.Formatter.getTwoDigitString_(
  126. Math.floor(time.getMilliseconds() / 10));
  127. };
  128. /**
  129. * Returns the number as a two-digit string, meaning it prepends a 0 if the
  130. * number if less than 10.
  131. * @param {number} n The number to format.
  132. * @return {string} A two-digit string representation of {@code n}.
  133. * @private
  134. */
  135. goog.debug.Formatter.getTwoDigitString_ = function(n) {
  136. if (n < 10) {
  137. return '0' + n;
  138. }
  139. return String(n);
  140. };
  141. /**
  142. * Returns a string for the number of seconds relative to the start time.
  143. * Prepads with spaces so that anything less than 1000 seconds takes up the
  144. * same number of characters for better formatting.
  145. * @param {goog.debug.LogRecord} logRecord The log to compare time to.
  146. * @param {number} relativeTimeStart The start time to compare to.
  147. * @return {string} The number of seconds of the LogRecord relative to the
  148. * start time.
  149. * @private
  150. */
  151. goog.debug.Formatter.getRelativeTime_ = function(logRecord, relativeTimeStart) {
  152. var ms = logRecord.getMillis() - relativeTimeStart;
  153. var sec = ms / 1000;
  154. var str = sec.toFixed(3);
  155. var spacesToPrepend = 0;
  156. if (sec < 1) {
  157. spacesToPrepend = 2;
  158. } else {
  159. while (sec < 100) {
  160. spacesToPrepend++;
  161. sec *= 10;
  162. }
  163. }
  164. while (spacesToPrepend-- > 0) {
  165. str = ' ' + str;
  166. }
  167. return str;
  168. };
  169. /**
  170. * Formatter that returns formatted html. See formatRecord for the classes
  171. * it uses for various types of formatted output.
  172. *
  173. * @param {string=} opt_prefix The prefix to place before text records.
  174. * @constructor
  175. * @extends {goog.debug.Formatter}
  176. */
  177. goog.debug.HtmlFormatter = function(opt_prefix) {
  178. goog.debug.Formatter.call(this, opt_prefix);
  179. };
  180. goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);
  181. /**
  182. * Exposes an exception that has been caught by a try...catch and outputs the
  183. * error as HTML with a stack trace.
  184. *
  185. * @param {*} err Error object or string.
  186. * @param {?Function=} fn If provided, when collecting the stack trace all
  187. * frames above the topmost call to this function, including that call,
  188. * will be left out of the stack trace.
  189. * @return {string} Details of exception, as HTML.
  190. */
  191. goog.debug.HtmlFormatter.exposeException = function(err, fn) {
  192. var html = goog.debug.HtmlFormatter.exposeExceptionAsHtml(err, fn);
  193. return goog.html.SafeHtml.unwrap(html);
  194. };
  195. /**
  196. * Exposes an exception that has been caught by a try...catch and outputs the
  197. * error with a stack trace.
  198. *
  199. * @param {*} err Error object or string.
  200. * @param {?Function=} fn If provided, when collecting the stack trace all
  201. * frames above the topmost call to this function, including that call,
  202. * will be left out of the stack trace.
  203. * @return {!goog.html.SafeHtml} Details of exception.
  204. */
  205. goog.debug.HtmlFormatter.exposeExceptionAsHtml = function(err, fn) {
  206. try {
  207. var e = goog.debug.normalizeErrorObject(err);
  208. // Create the error message
  209. var viewSourceUrl =
  210. goog.debug.HtmlFormatter.createViewSourceUrl_(e.fileName);
  211. var error = goog.html.SafeHtml.concat(
  212. goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
  213. 'Message: ' + e.message + '\nUrl: '),
  214. goog.html.SafeHtml.create(
  215. 'a', {href: viewSourceUrl, target: '_new'}, e.fileName),
  216. goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
  217. '\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' + e.stack +
  218. '-> ' +
  219. '[end]\n\nJS stack traversal:\n' + goog.debug.getStacktrace(fn) +
  220. '-> '));
  221. return error;
  222. } catch (e2) {
  223. return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
  224. 'Exception trying to expose exception! You win, we lose. ' + e2);
  225. }
  226. };
  227. /**
  228. * @param {?string=} fileName
  229. * @return {!goog.html.SafeUrl} SafeUrl with view-source scheme, pointing at
  230. * fileName.
  231. * @private
  232. */
  233. goog.debug.HtmlFormatter.createViewSourceUrl_ = function(fileName) {
  234. if (!goog.isDefAndNotNull(fileName)) {
  235. fileName = '';
  236. }
  237. if (!/^https?:\/\//i.test(fileName)) {
  238. return goog.html.SafeUrl.fromConstant(
  239. goog.string.Const.from('sanitizedviewsrc'));
  240. }
  241. var sanitizedFileName = goog.html.SafeUrl.sanitize(fileName);
  242. return goog.html.uncheckedconversions
  243. .safeUrlFromStringKnownToSatisfyTypeContract(
  244. goog.string.Const.from('view-source scheme plus HTTP/HTTPS URL'),
  245. 'view-source:' + goog.html.SafeUrl.unwrap(sanitizedFileName));
  246. };
  247. /**
  248. * Whether to show the logger exception text
  249. * @type {boolean}
  250. * @override
  251. */
  252. goog.debug.HtmlFormatter.prototype.showExceptionText = true;
  253. /**
  254. * Formats a record
  255. * @param {goog.debug.LogRecord} logRecord the logRecord to format.
  256. * @return {string} The formatted string as html.
  257. * @override
  258. */
  259. goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {
  260. if (!logRecord) {
  261. return '';
  262. }
  263. // OK not to use goog.html.SafeHtml.unwrap() here.
  264. return this.formatRecordAsHtml(logRecord).getTypedStringValue();
  265. };
  266. /**
  267. * Formats a record.
  268. * @param {goog.debug.LogRecord} logRecord the logRecord to format.
  269. * @return {!goog.html.SafeHtml} The formatted string as SafeHtml.
  270. * @override
  271. */
  272. goog.debug.HtmlFormatter.prototype.formatRecordAsHtml = function(logRecord) {
  273. if (!logRecord) {
  274. return goog.html.SafeHtml.EMPTY;
  275. }
  276. var className;
  277. switch (logRecord.getLevel().value) {
  278. case goog.debug.Logger.Level.SHOUT.value:
  279. className = 'dbg-sh';
  280. break;
  281. case goog.debug.Logger.Level.SEVERE.value:
  282. className = 'dbg-sev';
  283. break;
  284. case goog.debug.Logger.Level.WARNING.value:
  285. className = 'dbg-w';
  286. break;
  287. case goog.debug.Logger.Level.INFO.value:
  288. className = 'dbg-i';
  289. break;
  290. case goog.debug.Logger.Level.FINE.value:
  291. default:
  292. className = 'dbg-f';
  293. break;
  294. }
  295. // HTML for user defined prefix, time, logger name, and severity.
  296. var sb = [];
  297. sb.push(this.prefix_, ' ');
  298. if (this.showAbsoluteTime) {
  299. sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
  300. }
  301. if (this.showRelativeTime) {
  302. sb.push(
  303. '[', goog.debug.Formatter.getRelativeTime_(
  304. logRecord, this.startTimeProvider_.get()),
  305. 's] ');
  306. }
  307. if (this.showLoggerName) {
  308. sb.push('[', logRecord.getLoggerName(), '] ');
  309. }
  310. if (this.showSeverityLevel) {
  311. sb.push('[', logRecord.getLevel().name, '] ');
  312. }
  313. var fullPrefixHtml =
  314. goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(sb.join(''));
  315. // HTML for exception text and log record.
  316. var exceptionHtml = goog.html.SafeHtml.EMPTY;
  317. if (this.showExceptionText && logRecord.getException()) {
  318. exceptionHtml = goog.html.SafeHtml.concat(
  319. goog.html.SafeHtml.BR,
  320. goog.debug.HtmlFormatter.exposeExceptionAsHtml(
  321. logRecord.getException()));
  322. }
  323. var logRecordHtml = goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
  324. logRecord.getMessage());
  325. var recordAndExceptionHtml = goog.html.SafeHtml.create(
  326. 'span', {'class': className},
  327. goog.html.SafeHtml.concat(logRecordHtml, exceptionHtml));
  328. // Combine both pieces of HTML and, if needed, append a final newline.
  329. var html;
  330. if (this.appendNewline) {
  331. html = goog.html.SafeHtml.concat(
  332. fullPrefixHtml, recordAndExceptionHtml, goog.html.SafeHtml.BR);
  333. } else {
  334. html = goog.html.SafeHtml.concat(fullPrefixHtml, recordAndExceptionHtml);
  335. }
  336. return html;
  337. };
  338. /**
  339. * Formatter that returns formatted plain text
  340. *
  341. * @param {string=} opt_prefix The prefix to place before text records.
  342. * @constructor
  343. * @extends {goog.debug.Formatter}
  344. * @final
  345. */
  346. goog.debug.TextFormatter = function(opt_prefix) {
  347. goog.debug.Formatter.call(this, opt_prefix);
  348. };
  349. goog.inherits(goog.debug.TextFormatter, goog.debug.Formatter);
  350. /**
  351. * Formats a record as text
  352. * @param {goog.debug.LogRecord} logRecord the logRecord to format.
  353. * @return {string} The formatted string.
  354. * @override
  355. */
  356. goog.debug.TextFormatter.prototype.formatRecord = function(logRecord) {
  357. var sb = [];
  358. sb.push(this.prefix_, ' ');
  359. if (this.showAbsoluteTime) {
  360. sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');
  361. }
  362. if (this.showRelativeTime) {
  363. sb.push(
  364. '[', goog.debug.Formatter.getRelativeTime_(
  365. logRecord, this.startTimeProvider_.get()),
  366. 's] ');
  367. }
  368. if (this.showLoggerName) {
  369. sb.push('[', logRecord.getLoggerName(), '] ');
  370. }
  371. if (this.showSeverityLevel) {
  372. sb.push('[', logRecord.getLevel().name, '] ');
  373. }
  374. sb.push(logRecord.getMessage());
  375. if (this.showExceptionText) {
  376. var exception = logRecord.getException();
  377. if (exception) {
  378. var exceptionText =
  379. exception instanceof Error ? exception.message : exception.toString();
  380. sb.push('\n', exceptionText);
  381. }
  382. }
  383. if (this.appendNewline) {
  384. sb.push('\n');
  385. }
  386. return sb.join('');
  387. };
  388. /**
  389. * Formats a record as text
  390. * @param {goog.debug.LogRecord} logRecord the logRecord to format.
  391. * @return {!goog.html.SafeHtml} The formatted string as SafeHtml. This is
  392. * just an HTML-escaped version of the text obtained from formatRecord().
  393. * @override
  394. */
  395. goog.debug.TextFormatter.prototype.formatRecordAsHtml = function(logRecord) {
  396. return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(
  397. goog.debug.TextFormatter.prototype.formatRecord(logRecord));
  398. };