Path: blob/trunk/third_party/closure/goog/debug/formatter.js
2868 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Definition of various formatters for logging. Please minimize16* dependencies this file has on other closure classes as any dependency it17* takes won't be able to use the logging infrastructure.18*19*/2021goog.provide('goog.debug.Formatter');22goog.provide('goog.debug.HtmlFormatter');23goog.provide('goog.debug.TextFormatter');2425goog.require('goog.debug');26goog.require('goog.debug.Logger');27goog.require('goog.debug.RelativeTimeProvider');28goog.require('goog.html.SafeHtml');29goog.require('goog.html.SafeUrl');30goog.require('goog.html.uncheckedconversions');31goog.require('goog.string.Const');32333435/**36* Base class for Formatters. A Formatter is used to format a LogRecord into37* something that can be displayed to the user.38*39* @param {string=} opt_prefix The prefix to place before text records.40* @constructor41*/42goog.debug.Formatter = function(opt_prefix) {43this.prefix_ = opt_prefix || '';4445/**46* A provider that returns the relative start time.47* @type {goog.debug.RelativeTimeProvider}48* @private49*/50this.startTimeProvider_ =51goog.debug.RelativeTimeProvider.getDefaultInstance();52};535455/**56* Whether to append newlines to the end of formatted log records.57* @type {boolean}58*/59goog.debug.Formatter.prototype.appendNewline = true;606162/**63* Whether to show absolute time in the DebugWindow.64* @type {boolean}65*/66goog.debug.Formatter.prototype.showAbsoluteTime = true;676869/**70* Whether to show relative time in the DebugWindow.71* @type {boolean}72*/73goog.debug.Formatter.prototype.showRelativeTime = true;747576/**77* Whether to show the logger name in the DebugWindow.78* @type {boolean}79*/80goog.debug.Formatter.prototype.showLoggerName = true;818283/**84* Whether to show the logger exception text.85* @type {boolean}86*/87goog.debug.Formatter.prototype.showExceptionText = false;888990/**91* Whether to show the severity level.92* @type {boolean}93*/94goog.debug.Formatter.prototype.showSeverityLevel = false;959697/**98* Formats a record.99* @param {goog.debug.LogRecord} logRecord the logRecord to format.100* @return {string} The formatted string.101*/102goog.debug.Formatter.prototype.formatRecord = goog.abstractMethod;103104105/**106* Formats a record as SafeHtml.107* @param {goog.debug.LogRecord} logRecord the logRecord to format.108* @return {!goog.html.SafeHtml} The formatted string as SafeHtml.109*/110goog.debug.Formatter.prototype.formatRecordAsHtml = goog.abstractMethod;111112113/**114* Sets the start time provider. By default, this is the default instance115* but can be changed.116* @param {goog.debug.RelativeTimeProvider} provider The provider to use.117*/118goog.debug.Formatter.prototype.setStartTimeProvider = function(provider) {119this.startTimeProvider_ = provider;120};121122123/**124* Returns the start time provider. By default, this is the default instance125* but can be changed.126* @return {goog.debug.RelativeTimeProvider} The start time provider.127*/128goog.debug.Formatter.prototype.getStartTimeProvider = function() {129return this.startTimeProvider_;130};131132133/**134* Resets the start relative time.135*/136goog.debug.Formatter.prototype.resetRelativeTimeStart = function() {137this.startTimeProvider_.reset();138};139140141/**142* Returns a string for the time/date of the LogRecord.143* @param {goog.debug.LogRecord} logRecord The record to get a time stamp for.144* @return {string} A string representation of the time/date of the LogRecord.145* @private146*/147goog.debug.Formatter.getDateTimeStamp_ = function(logRecord) {148var time = new Date(logRecord.getMillis());149return goog.debug.Formatter.getTwoDigitString_((time.getFullYear() - 2000)) +150goog.debug.Formatter.getTwoDigitString_((time.getMonth() + 1)) +151goog.debug.Formatter.getTwoDigitString_(time.getDate()) + ' ' +152goog.debug.Formatter.getTwoDigitString_(time.getHours()) + ':' +153goog.debug.Formatter.getTwoDigitString_(time.getMinutes()) + ':' +154goog.debug.Formatter.getTwoDigitString_(time.getSeconds()) + '.' +155goog.debug.Formatter.getTwoDigitString_(156Math.floor(time.getMilliseconds() / 10));157};158159160/**161* Returns the number as a two-digit string, meaning it prepends a 0 if the162* number if less than 10.163* @param {number} n The number to format.164* @return {string} A two-digit string representation of {@code n}.165* @private166*/167goog.debug.Formatter.getTwoDigitString_ = function(n) {168if (n < 10) {169return '0' + n;170}171return String(n);172};173174175/**176* Returns a string for the number of seconds relative to the start time.177* Prepads with spaces so that anything less than 1000 seconds takes up the178* same number of characters for better formatting.179* @param {goog.debug.LogRecord} logRecord The log to compare time to.180* @param {number} relativeTimeStart The start time to compare to.181* @return {string} The number of seconds of the LogRecord relative to the182* start time.183* @private184*/185goog.debug.Formatter.getRelativeTime_ = function(logRecord, relativeTimeStart) {186var ms = logRecord.getMillis() - relativeTimeStart;187var sec = ms / 1000;188var str = sec.toFixed(3);189190var spacesToPrepend = 0;191if (sec < 1) {192spacesToPrepend = 2;193} else {194while (sec < 100) {195spacesToPrepend++;196sec *= 10;197}198}199while (spacesToPrepend-- > 0) {200str = ' ' + str;201}202return str;203};204205206207/**208* Formatter that returns formatted html. See formatRecord for the classes209* it uses for various types of formatted output.210*211* @param {string=} opt_prefix The prefix to place before text records.212* @constructor213* @extends {goog.debug.Formatter}214*/215goog.debug.HtmlFormatter = function(opt_prefix) {216goog.debug.Formatter.call(this, opt_prefix);217};218goog.inherits(goog.debug.HtmlFormatter, goog.debug.Formatter);219220221/**222* Exposes an exception that has been caught by a try...catch and outputs the223* error as HTML with a stack trace.224*225* @param {*} err Error object or string.226* @param {?Function=} fn If provided, when collecting the stack trace all227* frames above the topmost call to this function, including that call,228* will be left out of the stack trace.229* @return {string} Details of exception, as HTML.230*/231goog.debug.HtmlFormatter.exposeException = function(err, fn) {232var html = goog.debug.HtmlFormatter.exposeExceptionAsHtml(err, fn);233return goog.html.SafeHtml.unwrap(html);234};235236237/**238* Exposes an exception that has been caught by a try...catch and outputs the239* error with a stack trace.240*241* @param {*} err Error object or string.242* @param {?Function=} fn If provided, when collecting the stack trace all243* frames above the topmost call to this function, including that call,244* will be left out of the stack trace.245* @return {!goog.html.SafeHtml} Details of exception.246*/247goog.debug.HtmlFormatter.exposeExceptionAsHtml = function(err, fn) {248try {249var e = goog.debug.normalizeErrorObject(err);250// Create the error message251var viewSourceUrl =252goog.debug.HtmlFormatter.createViewSourceUrl_(e.fileName);253var error = goog.html.SafeHtml.concat(254goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(255'Message: ' + e.message + '\nUrl: '),256goog.html.SafeHtml.create(257'a', {href: viewSourceUrl, target: '_new'}, e.fileName),258goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(259'\nLine: ' + e.lineNumber + '\n\nBrowser stack:\n' + e.stack +260'-> ' +261'[end]\n\nJS stack traversal:\n' + goog.debug.getStacktrace(fn) +262'-> '));263return error;264} catch (e2) {265return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(266'Exception trying to expose exception! You win, we lose. ' + e2);267}268};269270271/**272* @param {?string=} fileName273* @return {!goog.html.SafeUrl} SafeUrl with view-source scheme, pointing at274* fileName.275* @private276*/277goog.debug.HtmlFormatter.createViewSourceUrl_ = function(fileName) {278if (!goog.isDefAndNotNull(fileName)) {279fileName = '';280}281if (!/^https?:\/\//i.test(fileName)) {282return goog.html.SafeUrl.fromConstant(283goog.string.Const.from('sanitizedviewsrc'));284}285var sanitizedFileName = goog.html.SafeUrl.sanitize(fileName);286return goog.html.uncheckedconversions287.safeUrlFromStringKnownToSatisfyTypeContract(288goog.string.Const.from('view-source scheme plus HTTP/HTTPS URL'),289'view-source:' + goog.html.SafeUrl.unwrap(sanitizedFileName));290};291292293294/**295* Whether to show the logger exception text296* @type {boolean}297* @override298*/299goog.debug.HtmlFormatter.prototype.showExceptionText = true;300301302/**303* Formats a record304* @param {goog.debug.LogRecord} logRecord the logRecord to format.305* @return {string} The formatted string as html.306* @override307*/308goog.debug.HtmlFormatter.prototype.formatRecord = function(logRecord) {309if (!logRecord) {310return '';311}312// OK not to use goog.html.SafeHtml.unwrap() here.313return this.formatRecordAsHtml(logRecord).getTypedStringValue();314};315316317/**318* Formats a record.319* @param {goog.debug.LogRecord} logRecord the logRecord to format.320* @return {!goog.html.SafeHtml} The formatted string as SafeHtml.321* @override322*/323goog.debug.HtmlFormatter.prototype.formatRecordAsHtml = function(logRecord) {324if (!logRecord) {325return goog.html.SafeHtml.EMPTY;326}327328var className;329switch (logRecord.getLevel().value) {330case goog.debug.Logger.Level.SHOUT.value:331className = 'dbg-sh';332break;333case goog.debug.Logger.Level.SEVERE.value:334className = 'dbg-sev';335break;336case goog.debug.Logger.Level.WARNING.value:337className = 'dbg-w';338break;339case goog.debug.Logger.Level.INFO.value:340className = 'dbg-i';341break;342case goog.debug.Logger.Level.FINE.value:343default:344className = 'dbg-f';345break;346}347348// HTML for user defined prefix, time, logger name, and severity.349var sb = [];350sb.push(this.prefix_, ' ');351if (this.showAbsoluteTime) {352sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');353}354if (this.showRelativeTime) {355sb.push(356'[', goog.debug.Formatter.getRelativeTime_(357logRecord, this.startTimeProvider_.get()),358's] ');359}360if (this.showLoggerName) {361sb.push('[', logRecord.getLoggerName(), '] ');362}363if (this.showSeverityLevel) {364sb.push('[', logRecord.getLevel().name, '] ');365}366var fullPrefixHtml =367goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(sb.join(''));368369// HTML for exception text and log record.370var exceptionHtml = goog.html.SafeHtml.EMPTY;371if (this.showExceptionText && logRecord.getException()) {372exceptionHtml = goog.html.SafeHtml.concat(373goog.html.SafeHtml.BR,374goog.debug.HtmlFormatter.exposeExceptionAsHtml(375logRecord.getException()));376}377var logRecordHtml = goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(378logRecord.getMessage());379var recordAndExceptionHtml = goog.html.SafeHtml.create(380'span', {'class': className},381goog.html.SafeHtml.concat(logRecordHtml, exceptionHtml));382383384// Combine both pieces of HTML and, if needed, append a final newline.385var html;386if (this.appendNewline) {387html = goog.html.SafeHtml.concat(388fullPrefixHtml, recordAndExceptionHtml, goog.html.SafeHtml.BR);389} else {390html = goog.html.SafeHtml.concat(fullPrefixHtml, recordAndExceptionHtml);391}392return html;393};394395396397/**398* Formatter that returns formatted plain text399*400* @param {string=} opt_prefix The prefix to place before text records.401* @constructor402* @extends {goog.debug.Formatter}403* @final404*/405goog.debug.TextFormatter = function(opt_prefix) {406goog.debug.Formatter.call(this, opt_prefix);407};408goog.inherits(goog.debug.TextFormatter, goog.debug.Formatter);409410411/**412* Formats a record as text413* @param {goog.debug.LogRecord} logRecord the logRecord to format.414* @return {string} The formatted string.415* @override416*/417goog.debug.TextFormatter.prototype.formatRecord = function(logRecord) {418var sb = [];419sb.push(this.prefix_, ' ');420if (this.showAbsoluteTime) {421sb.push('[', goog.debug.Formatter.getDateTimeStamp_(logRecord), '] ');422}423if (this.showRelativeTime) {424sb.push(425'[', goog.debug.Formatter.getRelativeTime_(426logRecord, this.startTimeProvider_.get()),427's] ');428}429430if (this.showLoggerName) {431sb.push('[', logRecord.getLoggerName(), '] ');432}433if (this.showSeverityLevel) {434sb.push('[', logRecord.getLevel().name, '] ');435}436sb.push(logRecord.getMessage());437if (this.showExceptionText) {438var exception = logRecord.getException();439if (exception) {440var exceptionText =441exception instanceof Error ? exception.message : exception.toString();442sb.push('\n', exceptionText);443}444}445if (this.appendNewline) {446sb.push('\n');447}448return sb.join('');449};450451452/**453* Formats a record as text454* @param {goog.debug.LogRecord} logRecord the logRecord to format.455* @return {!goog.html.SafeHtml} The formatted string as SafeHtml. This is456* just an HTML-escaped version of the text obtained from formatRecord().457* @override458*/459goog.debug.TextFormatter.prototype.formatRecordAsHtml = function(logRecord) {460return goog.html.SafeHtml.htmlEscapePreservingNewlinesAndSpaces(461goog.debug.TextFormatter.prototype.formatRecord(logRecord));462};463464465