blob: c6424fc8cabe0efd1e5bafa288a1c815807c89c7 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Log manager which enables and collects both fine and info logs,
* and provides method to get logs with parameter to include or exclude
* fine logs.
*/
goog.module('mr.LogManager');
goog.module.declareLegacyNamespace();
const Config = goog.require('mr.Config');
const FixedSizeQueue = goog.require('mr.FixedSizeQueue');
const Logger = goog.require('mr.Logger');
const PersistentData = goog.require('mr.PersistentData');
const PersistentDataManager = goog.require('mr.PersistentDataManager');
/**
* @implements {PersistentData}
*/
class LogManager {
constructor() {
/** @private @const */
this.buffer_ = new FixedSizeQueue(LogManager.BUFFER_SIZE);
/** @private @const */
this.startTime_ = Date.now();
}
/**
* @return {!LogManager}
*/
static getInstance() {
if (LogManager.instance_ == null) {
LogManager.instance_ = new LogManager();
}
return LogManager.instance_;
}
/**
* Init
*/
init() {
Logger.level = this.getDefaultLogLevel_();
const browserLogger = Logger.getInstance('browser');
const oldErrorHandler = window.onerror;
/**
* @param {string} message
* @param {string} url
* @param {number} line
* @param {number=} col
* @param {*=} error
*/
window.onerror = (message, url, line, col, error) => {
if (oldErrorHandler) {
oldErrorHandler(message, url, line, col, error);
}
browserLogger.error(`Error: ${message} (${url} @ Line: ${line})`, error);
};
Logger.addHandler(this.onNewLog_.bind(this));
// Override log level via localStorage setting
const debugKey = 'debug.logs';
const debugLevel = window.localStorage[debugKey];
if (debugLevel) {
Logger.level =
Logger.stringToLevel(debugLevel.toUpperCase(), Logger.Level.FINE);
} else if (!Config.isPublicChannel) {
// Record the default local level in local settings so developers can
// easily change it without having to look up the name of the setting.
window.localStorage[debugKey] =
Logger.levelToString(Logger.DEFAULT_LEVEL);
}
const consoleKey = 'debug.console';
if (!Config.isUnitTest && !Config.isPublicChannel &&
window.localStorage[consoleKey] == null) {
// Enable console logging by default in internal builds, but not in unit
// tests. Unit tests are excluded because logs written to the console
// make it hard to spot output from the test framework.
//
// Any value other than 'false' or '' is treated as true.
window.localStorage[consoleKey] = 'true';
}
const consoleValue = window.localStorage[consoleKey];
if (consoleValue && consoleValue.toLowerCase() != 'false') {
Logger.addHandler(this.logToConsole_.bind(this));
}
}
/**
* Saves logs in the internal buffer.
*
* @param {Logger.Record} logRecord The log entry.
* @private
*/
onNewLog_(logRecord) {
this.buffer_.enqueue(this.formatRecord_(logRecord, false));
const exception = logRecord.exception;
if (exception instanceof Error && exception.stack) {
this.buffer_.enqueue(exception.stack);
}
}
/**
* @param {Logger.Record} logRecord The log entry.
* @private
*/
logToConsole_(logRecord) {
const args = [this.formatRecord_(logRecord, true)];
if (logRecord.exception) {
args.push(logRecord.exception);
}
switch (logRecord.level) {
case Logger.Level.SEVERE:
console.error(...args);
break;
case Logger.Level.WARNING:
console.warn(...args);
break;
case Logger.Level.INFO:
console.log(...args);
break;
default:
console.debug(...args);
}
}
/**
* @param {!Logger.Record} record
* @param {boolean} forConsole
* @return {string}
* @private
*/
formatRecord_(record, forConsole) {
const sb = ['['];
if (forConsole) {
// Format relative timestamp.
const seconds = (Date.now() - this.startTime_) / 1000;
sb.push((' ' + seconds.toFixed(3)).slice(-7));
} else {
// Format absolute timestamp.
const date = new Date(record.time);
const twoDigitStr = num => num < 10 ? '0' + num : num;
sb.push(
date.getFullYear().toString(), '-', twoDigitStr(date.getMonth() + 1),
'-', twoDigitStr(date.getDate()), ' ', twoDigitStr(date.getHours()),
':', twoDigitStr(date.getMinutes()), ':',
twoDigitStr(date.getSeconds()), '.',
twoDigitStr(Math.floor(date.getMilliseconds() / 10)));
}
sb.push(
'][', Logger.levelToString(record.level), '][', record.logger, '] ',
record.message);
// Don't append the exception when logging to the console, because it will
// be handled specially later.
if (!forConsole && record.exception != null) {
sb.push('\n');
if (record.exception instanceof Error) {
sb.push(record.exception.message);
} else {
try {
sb.push(JSON.stringify(record.exception));
} catch (e) {
sb.push(record.exception.toString());
}
}
}
sb.push('\n');
return sb.join('');
}
/**
* Get the logs in log buffer.
* @return {string}
*/
getLogs() {
if (this.buffer_.getCount() == 0) {
return 'NA';
}
return this.buffer_.getValues().join('');
}
/**
* @return {!Logger.Level} The default log level.
* @private
*/
getDefaultLogLevel_() {
return Config.isPublicChannel ? Logger.Level.INFO : Logger.Level.FINE;
}
/**
* Registers with the data manager and loads any previous logs.
*/
registerDataManager() {
PersistentDataManager.register(this);
}
/**
* @override
*/
getStorageKey() {
return 'LogManager';
}
/**
* @override
*/
getData() {
return [this.buffer_.getValues()];
}
/**
* @override
*/
loadSavedData() {
const currentLogs = this.buffer_.getValues();
this.buffer_.clear();
for (let log of PersistentDataManager.getTemporaryData(this) || []) {
this.buffer_.enqueue(log);
}
for (let log of currentLogs) {
this.buffer_.enqueue(log);
}
}
}
/**
* @private {?LogManager}
*/
LogManager.instance_ = null;
/**
* The max number of logs in buffer. The old logs get pushed out when the buffer
* is full.
* @const
*/
LogManager.BUFFER_SIZE = 1000;
exports = LogManager;