@oat-sa/tao-core-sdk
Version:
Core libraries of TAO
298 lines (275 loc) • 10.3 kB
JavaScript
define(['lodash', 'core/format', 'core/moduleLoader'], function (_, format, moduleLoader) { 'use strict';
_ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
format = format && Object.prototype.hasOwnProperty.call(format, 'default') ? format['default'] : format;
moduleLoader = moduleLoader && Object.prototype.hasOwnProperty.call(moduleLoader, 'default') ? moduleLoader['default'] : moduleLoader;
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2017-2019 (original work) Open Assessment Technologies SA;
*
*/
/**
* The default level
*/
let defaultLevel = 'info';
const levels = {
fatal: 60,
// The service/app is going to stop or become unusable now. An operator should definitely look into this soon.
error: 50,
// Fatal for a particular request, but the service/app continues servicing other requests. An operator should look at this soon(ish).
warn: 40,
// A note on something that should probably be looked at by an operator eventually.
info: 30,
// Detail on regular operation.
debug: 20,
// Anything else, i.e. too verbose to be included in "info" level.
trace: 10 // Logging from external libraries used by your app or very detailed application logging.
};
/**
* Major version of the node-bunyan package (for compat)
*/
const bunyanVersion = 0;
/**
* Where messages dwells
*/
let logQueue = [];
/**
* Get the actual level as a string,
* fallback to the default level.
* @param {String|Number} [level] - the level
* @returns {String} the level
*/
function getLevel(level) {
if (typeof level === 'undefined' || _.isString(level) && !_.has(levels, level)) {
return defaultLevel;
}
if (_.isNumber(level)) {
return _.findKey(levels, function (l) {
return l === level;
}) || defaultLevel;
}
return level;
}
/**
* Get the actual level as a number,
* fallback to the default level.
* @param {String|Number} [level] - the level
* @returns {Number} the level
*/
function getLevelNum(level) {
if (_.isString(level) && _.has(levels, level)) {
return levels[level];
}
if (_.isNumber(level) && _.includes(levels, level)) {
return level;
}
return levels[defaultLevel];
}
/**
* Check whether the given level is above the minimum level threshold
* @param {String|Number} minLevel - the minimum level
* @param {String|Number} [level] - the level to check
* @returns {Boolean}
*/
function checkMinLevel(minLevel, level) {
return getLevelNum(level) >= getLevelNum(minLevel);
}
/**
* Creates a logger instance
*
* @param {String} name - each logger instance MUST have a name
* @param {String|Number} [minLevel] - the minimum logging level
* @param {Object} [fields] - fields to add to all records
*
* @returns {logger} a new logger instance
*/
function loggerFactory(name, minLevel, fields) {
if (!_.isString(name) || _.isEmpty(name)) {
throw new TypeError('A logger needs a name');
}
if (_.isPlainObject(minLevel) && typeof field === 'undefined') {
fields = minLevel;
minLevel = defaultLevel;
}
const baseRecord = _.defaults(fields || {}, {
name: name,
pid: 1,
// only for compat
hostname: navigator.userAgent
});
/**
* Exposes a log method and one by log level, like logger.trace()
*
* @typedef logger
*/
const logger = {
/**
* Log messages by delegating to the provider
*
* @param {String|Number} level - the log level
* @param {Object|string} [recordFields] - fields to add to the log record
* @param {String|Error} message - the message to log
* @param {...String} [rest] - rest parameters if the message is formatted
* @returns {logger} chains
*/
log(level, recordFields, message) {
for (var _len = arguments.length, rest = new Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
rest[_key - 3] = arguments[_key];
}
let err;
const time = new Date().toISOString();
//without providers or not the level, we don't log.
if (loggerFactory.providers === false || !checkMinLevel(minLevel || defaultLevel, level)) {
return this;
}
if (_.isString(recordFields) || recordFields instanceof Error) {
if ('undefined' !== typeof message) {
rest = [message, ...rest];
}
message = recordFields;
recordFields = {};
}
const record = {
level: getLevel(level),
v: bunyanVersion,
time: time
};
if (checkMinLevel(levels.error, level) || message instanceof Error) {
if (message instanceof Error) {
err = message;
} else {
message = _.isObject(message) ? JSON.stringify(message) : message;
err = new Error(message);
}
record.msg = err.message;
record.err = err;
} else {
record.msg = format(message, ...rest);
}
_.merge(record, baseRecord, recordFields);
logQueue.push(record);
loggerFactory.flush();
return this;
},
/**
* Get/set the default level of the logger
* @param {String|Number} [value] - set the default level
* @returns {String|logger} the default level as a getter or chains as a setter
*/
level(value) {
if (typeof value !== 'undefined') {
//update the partial function
minLevel = getLevelNum(value);
return this;
}
return getLevel(minLevel);
},
/**
* Fork the current logger to create a child logger :
* same config + child fields
*
* @param {Object} [childFields] - specialized child fields
* @returns {logger} the child logger
*/
child(childFields) {
return loggerFactory(name, minLevel, _.defaults(childFields, baseRecord));
}
};
//augment the logger by each level
return _.reduce(levels, function reduceLogLevel(target, level, levelName) {
target[levelName] = _.partial(logger.log, level);
return target;
}, logger);
}
/**
* Exposes the levels
* @type {Object}
*/
loggerFactory.levels = levels;
/**
* The list of providers bound to the logger.
* @type {Boolean|Array} false means we don't log, array even empty we keep the logs
*/
loggerFactory.providers = false;
/**
* Load providers from AMD modules
* @param {Object} providerConfigs - provider's modules to load and register
* @returns {Promise} resolves once modules are registered
*/
loggerFactory.load = function load(providerConfigs) {
this.providers = [];
//we can load the loggers dynamically
const modules = Object.keys(providerConfigs || {}).map(module => ({
module,
category: 'logger'
}));
return moduleLoader().addList(modules).load().then(loadedProviders => {
loadedProviders.forEach((provider, moduleKey) => {
const providerConfig = modules[moduleKey] && modules[moduleKey].module && providerConfigs[modules[moduleKey].module];
this.register(provider, providerConfig);
});
})
//flush messages that arrived before the providers are there
.then(() => this.flush());
};
/**
* A logger provider provides with a way to log
* @typedef {Object} loggerProvider
* @property {Function} log - called with the message in parameter
*/
/**
* Registers a logger provider.
* @param {loggerProvider} provider
* @param {object} providerConfig - provider's config
* @throws TypeError
*/
loggerFactory.register = function register(provider, providerConfig) {
if (!_.isPlainObject(provider) || !_.isFunction(provider.log)) {
throw new TypeError('A log provider is an object with a log method');
}
//propogate checkMinLevel function
provider.checkMinLevel = checkMinLevel;
if (_.isFunction(provider.setConfig)) {
provider.setConfig(providerConfig);
}
this.providers = this.providers || [];
this.providers.push(provider);
};
/**
* Flush the messages queue into the providers
*/
loggerFactory.flush = function flush() {
if (_.isArray(this.providers) && this.providers.length > 0) {
_.forEach(logQueue, function (message) {
//forward to the providers
_.forEach(loggerFactory.providers, function (provider) {
provider.log(message);
});
});
//clear the queue
logQueue = [];
}
};
/**
* Change the default level for all loggers
* @param {String|Number} [level] - set the default level
* @returns {String} the defined level
*/
loggerFactory.setDefaultLevel = function setDefaultLevel(level) {
defaultLevel = getLevel(level);
return defaultLevel;
};
return loggerFactory;
});