blob: 9606acbc49bb37c696d9b858b0d77045c78dfe0e [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.provide('mr.LogManager');
goog.require('mr.Config');
goog.require('mr.FixedSizeQueue');
goog.require('mr.Logger');
goog.require('mr.PersistentData');
goog.require('mr.PersistentDataManager');
/**
* @implements {mr.PersistentData}
*/
mr.LogManager = class {
constructor() {
/** @private @const */
this.buffer_ = new mr.FixedSizeQueue(mr.LogManager.BUFFER_SIZE);
/** @private @const */
this.startTime_ = Date.now();
}
/**
* @return {!mr.LogManager}
*/
static getInstance() {
if (mr.LogManager.instance_ == null) {
mr.LogManager.instance_ = new mr.LogManager();
}
return mr.LogManager.instance_;
}
/**
* Init
*/
init() {
mr.Logger.level = this.getDefaultLogLevel_();
const browserLogger = mr.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);
};
mr.Logger.addHandler(this.onNewLog_.bind(this));
// Override log level via localStorage setting
const debugLevel = window.localStorage['debug.logs'];
if (debugLevel) {
mr.Logger.level = mr.Logger.stringToLevel(
debugLevel.toUpperCase(), mr.Logger.Level.FINE);
}
if (window.localStorage['debug.console']) {
mr.Logger.addHandler(this.logToConsole_.bind(this));
}
}
/**
* Saves logs in the internal buffer.
*
* @param {mr.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 {mr.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 mr.Logger.Level.SEVERE:
console.error(...args);
break;
case mr.Logger.Level.WARNING:
console.warn(...args);
break;
case mr.Logger.Level.INFO:
console.log(...args);
break;
default:
console.debug(...args);
}
}
/**
* @param {!mr.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(
'][', mr.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 {!mr.Logger.Level} The default log level.
* @private
*/
getDefaultLogLevel_() {
return mr.Config.isPublicChannel ? mr.Logger.Level.INFO :
mr.Logger.Level.FINE;
}
/**
* Registers with the data manager and loads any previous logs.
*/
registerDataManager() {
mr.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 mr.PersistentDataManager.getTemporaryData(this) || []) {
this.buffer_.enqueue(log);
}
for (let log of currentLogs) {
this.buffer_.enqueue(log);
}
}
};
/**
* @private {mr.LogManager}
*/
mr.LogManager.instance_ = null;
/**
* The max number of logs in buffer. The old logs get pushed out when the buffer
* is full.
* @const
*/
mr.LogManager.BUFFER_SIZE = 1000;