twilio-video
Version:
Twilio Video JavaScript Library
295 lines (261 loc) • 8.17 kB
JavaScript
/* eslint new-cap:0 */
;
const defaultGetLogger = require('../vendor/loglevel').getLogger;
const constants = require('./constants');
const { DEFAULT_LOG_LEVEL, DEFAULT_LOGGER_NAME } = constants;
const E = require('./constants').typeErrors;
let deprecationWarningsByComponentConstructor;
function getDeprecationWarnings(componentConstructor) {
deprecationWarningsByComponentConstructor = deprecationWarningsByComponentConstructor || new Map();
if (deprecationWarningsByComponentConstructor.has(componentConstructor)) {
return deprecationWarningsByComponentConstructor.get(componentConstructor);
}
const deprecationWarnings = new Set();
deprecationWarningsByComponentConstructor.set(componentConstructor, deprecationWarnings);
return deprecationWarnings;
}
/**
* Selectively outputs messages to console based on specified minimum module
* specific log levels.
*
* NOTE: The values in the logLevels object passed to the constructor is changed
* by subsequent calls to {@link Log#setLevels}.
*/
class Log {
/**
* Construct a new {@link Log} object.
* @param {String} moduleName - Name of the logging module (webrtc/media/signaling)
* @param {object} component - Component owning this instance of {@link Log}
* @param {LogLevels} logLevels - Logging levels. See {@link LogLevels}
* @param {String} loggerName - Name of the logger instance. Used when calling getLogger from loglevel module
* @param {Function} [getLogger] - optional method used internally.
*/
constructor(moduleName, component, logLevels, loggerName, getLogger) {
if (typeof moduleName !== 'string') {
throw E.INVALID_TYPE('moduleName', 'string');
}
if (!component) {
throw E.REQUIRED_ARGUMENT('component');
}
if (typeof logLevels !== 'object') {
logLevels = {};
}
getLogger = getLogger || defaultGetLogger;
validateLogLevels(logLevels);
/* istanbul ignore next */
Object.defineProperties(this, {
_component: {
value: component
},
_logLevels: {
value: logLevels
},
_warnings: {
value: new Set()
},
_loggerName: {
get: function get() {
let name = loggerName && typeof loggerName === 'string' ? loggerName : DEFAULT_LOGGER_NAME;
if (!this._logLevelsEqual) {
name = `${name}-${moduleName}`;
}
return name;
}
},
_logger: {
get: function get() {
const logger = getLogger(this._loggerName);
let level = this._logLevels[moduleName] || DEFAULT_LOG_LEVEL;
// There is no 'off' in the logger module. It uses 'silent' instead
level = level === 'off' ? 'silent' : level;
logger.setDefaultLevel(level);
return logger;
}
},
_logLevelsEqual: {
get: function get() {
// True if all levels are the same
return (new Set(Object.values(this._logLevels)).size) === 1;
}
},
logLevel: {
get: function get() {
return Log.getLevelByName(logLevels[moduleName] || DEFAULT_LOG_LEVEL);
}
},
name: { get: component.toString.bind(component) }
});
}
/**
* Get the log level (number) by its name (string)
* @param {String} name - Name of the log level
* @returns {Number} Requested log level
* @throws {TwilioError} INVALID_LOG_LEVEL (32056)
* @public
*/
static getLevelByName(name) {
if (!isNaN(name)) {
return parseInt(name, 10);
}
name = name.toUpperCase();
validateLogLevel(name);
return Log[name];
}
/**
* Create a child {@link Log} instance with this._logLevels
* @param moduleName - Name of the logging module
* @param component - Component owning this instance of {@link Log}
* @returns {Log} this
*/
createLog(moduleName, component) {
let name = this._loggerName;
// Grab the original logger name
if (!this._logLevelsEqual) {
name = name.substring(0, name.lastIndexOf('-'));
}
return new Log(moduleName, component, this._logLevels, name);
}
/**
* Set new log levels.
* This changes the levels for all its ancestors,
* siblings, and children and descendants instances of {@link Log}.
* @param {LogLevels} levels - New log levels
* @throws {TwilioError} INVALID_ARGUMENT
* @returns {Log} this
*/
setLevels(levels) {
validateLogLevels(levels);
Object.assign(this._logLevels, levels);
return this;
}
/**
* Log a message using the logger method appropriate for the specified logLevel
* @param {Number} logLevel - Log level of the message being logged
* @param {Array} messages - Message(s) to log
* @returns {Log} This instance of {@link Log}
* @public
*/
log(logLevel, messages) {
let name = Log._levels[logLevel];
// eslint-disable-next-line no-use-before-define
if (!name) { throw E.INVALID_VALUE('logLevel', LOG_LEVEL_VALUES); }
name = name.toLowerCase();
const prefix = [new Date().toISOString(), name, this.name];
(this._logger[name] || function noop() {})(...prefix.concat(messages));
return this;
}
/**
* Log a debug message
* @param {...String} messages - Message(s) to pass to the logger
* @returns {Log} This instance of {@link Log}
* @public
*/
debug() {
return this.log(Log.DEBUG, [].slice.call(arguments));
}
/**
* Log a deprecation warning. Deprecation warnings are logged as warnings and
* they are only ever logged once.
* @param {String} deprecationWarning - The deprecation warning
* @returns {Log} This instance of {@link Log}
* @public
*/
deprecated(deprecationWarning) {
const deprecationWarnings = getDeprecationWarnings(this._component.constructor);
if (deprecationWarnings.has(deprecationWarning)) {
return this;
}
deprecationWarnings.add(deprecationWarning);
return this.warn(deprecationWarning);
}
/**
* Log an info message
* @param {...String} messages - Message(s) to pass to the logger
* @returns {Log} This instance of {@link Log}
* @public
*/
info() {
return this.log(Log.INFO, [].slice.call(arguments));
}
/**
* Log a warn message
* @param {...String} messages - Message(s) to pass to the logger
* @returns {Log} This instance of {@link Log}
* @public
*/
warn() {
return this.log(Log.WARN, [].slice.call(arguments));
}
/**
* Log a warning once.
* @param {String} warning
* @returns {Log} This instance of {@link Log}
* @public
*/
warnOnce(warning) {
if (this._warnings.has(warning)) {
return this;
}
this._warnings.add(warning);
return this.warn(warning);
}
/**
* Log an error message
* @param {...String} messages - Message(s) to pass to the logger
* @returns {Log} This instance of {@link Log}
* @public
*/
error() {
return this.log(Log.ERROR, [].slice.call(arguments));
}
/**
* Log an error message and throw an exception
* @param {TwilioError} error - Error to throw
* @param {String} customMessage - Custom message for the error
* @public
*/
throw(error, customMessage) {
if (error.clone) {
error = error.clone(customMessage);
}
this.log(Log.ERROR, error);
throw error;
}
}
// Singleton Constants
/* eslint key-spacing:0 */
/* istanbul ignore next */
Object.defineProperties(Log, {
DEBUG: { value: 0 },
INFO: { value: 1 },
WARN: { value: 2 },
ERROR: { value: 3 },
OFF: { value: 4 },
_levels: {
value: [
'DEBUG',
'INFO',
'WARN',
'ERROR',
'OFF',
]
}
});
const LOG_LEVELS_SET = {};
const LOG_LEVEL_VALUES = [];
const LOG_LEVEL_NAMES = Log._levels.map((level, i) => {
LOG_LEVELS_SET[level] = true;
LOG_LEVEL_VALUES.push(i);
return level;
});
function validateLogLevel(level) {
if (!(level in LOG_LEVELS_SET)) {
throw E.INVALID_VALUE('level', LOG_LEVEL_NAMES);
}
}
function validateLogLevels(levels) {
Object.keys(levels).forEach(moduleName => {
validateLogLevel(levels[moduleName].toUpperCase());
});
}
module.exports = Log;