src/Logs.js
import Logger from 'js-logger';
const singleton = Symbol();
const singletonEnforcer = Symbol();
/**
* @protected
* Class that provides logger instances
* Each log instance has an associated name used to identify the file
* that created log records.
*
* This class implements a singleton pattern. Instances of {Logs} are created
* with Logs.instance
*
* @example
* // Log a debug message
* var log = Logs.instance.getLogger('test.js');
* log.debug('a message');
*
* // Generates next log message:
* // [14:55:03] test.js a message
*
* @example
* // Log an error message
* var log = Logs.instance.getLogger('test.js');
* log.error('an error message');
*
* // Generates next error message:
* // [14:55:03] test.js a message
*/
export default class Logs {
/**
* Get a {Logs} instance
*
* @type {Logs}
*/
static get instance() {
if (!this[singleton]) {
this[singleton] = new Logs(singletonEnforcer);
}
return this[singleton];
}
/**
* Class constructor
*
* @private
*
* @param {Symbol} enforcer symbol used internally to implement singleton access
*/
constructor(enforcer) {
if (enforcer != singletonEnforcer) {
throw new Error('The constructor is private, use Logs.instance instead');
}
this._handlers = [];
Logger.useDefaults();
Logger.setHandler((messages, context) => {
this._handlers.forEach((handler) => {
handler(messages, context);
});
});
this.addHandler(Logger.createDefaultHandler({formatter: this._formatter}));
}
/**
* Supported log levels
*
* @type {Object}
*/
get LEVELS() {
return {
DEBUG: Logger.DEBUG,
INFO: Logger.INFO,
TIME: Logger.TIME,
WARN: Logger.WARN,
ERROR: Logger.ERROR,
OFF: Logger.OFF,
};
}
/**
* Get a stored log level associated with given name.
*
* If environment doesn't support localStorage Logs.LEVELS.OFF is returned
*
* Log levels are stored as a comma separated list in localStorage.sippodebug. Each
* element of this list follows next syntax: "name<=level>". level defaults to DEBUG.
*
* @param {String} name element name
*
* @return {Object} log level
*/
getLevelFromLocalStorage(name) {
if (!localStorage || !localStorage.sippodebug) {
return this.LEVELS.WARN;
}
let any = false;
let entries = localStorage.sippodebug.split(',').map((entry) => {
let [id, value] = entry.split('=');
any = any || entry === '*';
return {
id: id,
value: value || 'DEBUG',
};
});
for (let i = 0; i < entries.length; i++) {
let entry = entries[i];
if (entry.id === name && this.LEVELS[entry.value]) {
return this.LEVELS[entry.value];
}
}
return any ? this.LEVELS.DEBUG : this.LEVELS.WARN;
}
/**
* Get a js-logger named logger
*
* @param {String} name logger identifier
*
* @return {Logger} js-logger named instance
*/
getLogger(name) {
let logger = Logger.get(name);
logger.setLevel(this.getLevelFromLocalStorage(name));
return logger;
}
/**
* Set global log level
*
* @param {String} name named logger identifier
* @param {Object} level selected log level.
*
* @return {Void} this method doesn't return anything
*/
setLevel(name, level) {
Logger.get(name).setLevel(level);
}
/**
* Add log handler: function called every time a log message is
* created.
*
* @param {Function} fn log handler. Expects two arguments; the first
* being the log messages to output and the latter being a context
* object which can be inspected by the log handler.
*
* @return {Logs} self
*/
addHandler(fn) {
if (this._handlers.indexOf(fn) === -1) {
this._handlers.push(fn);
}
return this;
}
/**
* Remove a log handler
*
* @param {Function} fn log handler
*
* @return {Logs} self
*/
removeHandler(fn) {
this._handlers = this._handlers.filter(function(handler) {
return handler !== fn;
});
}
/**
* Add Logger name and current time to log messages.
*
* @private
*
* @param {Array<Object>} messages message array to log
* @param {Object} context js-logger context
*
* @return {Void} this method doesn't return anything
*/
_formatter(messages, context) {
let time = new Date().toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1');
for (let i = 0; i < messages.length; i++) {
if (messages[i] instanceof Error && messages[i].stack) {
messages[i] = messages[i].stack;
}
}
messages.unshift(`[${time}] ${context.name}`);
}
}