Files
Zos/Skills/@be/node_modules/jibo-log/lib/jibo-log.js

1455 lines
55 KiB
JavaScript

(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.jiboLog = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const output_handlers_1 = require("./output-handlers");
const defaultConfig_1 = require("./defaultConfig");
const utils_1 = require("./utils");
const getPlatformVersion_1 = require("./utils/getPlatformVersion");
const types_1 = require("./types");
const Level_1 = require("./types/Level");
const LogOutputs_1 = require("./LogOutputs");
const utils_2 = require("./utils");
const PromiseUtils_1 = require("./utils/PromiseUtils");
const fs = require("fs");
const fsx = require("fs-extra");
const os = require("os");
const osHomedir = require("os-homedir");
const path = require("path");
const NAMESPACE_DELIMITER = '.';
const prify = PromiseUtils_1.PromiseUtils.promisify;
/**
* Log class - centralized logging facility
* @class Log
* @param {string} [namespace=''] The namespace to log to
* @param {Log} [parent] The log parent (prepended to the namespace)
*/
class Log {
constructor(namespace = '', parent) {
/**
* The robot ID, defaulting to the hostname of the current machine
* It is useful to have this public, so a cloud process can set the
* robot ID based on the robot credentials provided upon connection
* @name Log#robotID
* @type {string}
*/
this.robotID = os.hostname();
// In the Pegasus environment, we need to support a separate configuration
// per robot
this._outputPerNamespace = {};
this.namespace =
(parent && parent.namespace)
? `${parent.namespace}${NAMESPACE_DELIMITER}${namespace}`
: namespace;
if (!Log._staticNamespaceLevels) {
this.outputPerNamespace =
parent
? parent._outputPerNamespace
: Log._outputPerNamespace;
}
}
/**
* The level of each output channel of the global logger
* @static
* @readonly
* @name Log.outputs
* @type {LogOutputs}
*/
static get outputs() {
return Log.global.outputs;
}
/**
* Load the default logging configuration statically
* @static
* @method Log.initialize
* @return {Promise<void>}
*/
static initialize() {
return __awaiter(this, void 0, void 0, function* () {
if (!Log._initialized) {
Log._outputPerNamespace = {};
// Set up the output handlers
Log._outputHandlers = output_handlers_1.outputHandlers;
// Load the default logging configuration
Log._loadConfig(defaultConfig_1.default);
// See if there's a local config file, and load it up if so
Log._localConfigPath = process.env.JIBO_LOG_CONFIG ||
(Log._onRobot
? '/var/jibo/t.logging.json'
: path.resolve(osHomedir(), '.jibo', 't.logging.json'));
yield Log._loadLocalConfig();
if (Log._onRobot) {
// By this point, globally uncaught exception and unhandled
// promise rejection handlers have been attached, so stifle
// stdout and stderr to avoid System Manager from logging
// console output from Node processes to syslog (and thus
// double-logging). This allows use of DevTools Console to
// monitor logs in real time
const nullStream = new utils_2.NullStream();
process.stdout.write = nullStream.write.bind(nullStream);
process.stderr.write = nullStream.write.bind(nullStream);
}
// Don't set the initialized flag until after we've tried loading a
// local config file
Log._initialized = true;
}
});
}
/**
* Add an output handler to jibo-log
* @method Log.addOutputHandler
* @param {string} name The name of the output channel
* @param {OutputHandler<Config extends Object>} handler The handler to add
*/
static addOutputHandler(name, handler) {
Log._outputHandlers[name] = handler;
}
/**
* Set the logging configuration from input (e.g. parsed from JSON file)
* @static
* @method Log.loadConfig
* @param {LoggingConfig} loggingConfig The configuration to load
* @return {Promise<void>}
*/
// Load local configuration (on- or off-robot) after processes load their
// own configuration, so that local configuration takes precedence
static loadConfig(loggingConfig) {
return __awaiter(this, void 0, void 0, function* () {
// Wait for Log to finish its initialization, to avoid race condition
// for its own loading of the local config file
while (!Log._initialized) {
yield prify((cb) => setImmediate(cb));
}
// If integration tests indicate that local config should be ignored,
// we need to clear the already-loaded local config, and reload the
// jibo-log defaults
if (loggingConfig.skipLocalConfig) {
Log._outputPerNamespace = {};
Log._loadConfig(defaultConfig_1.default);
}
Log._loadConfig(loggingConfig);
// If there's a local config file, load it afterwards so it takes
// precedence. _skipLocalConfig check is so integration tests can
// ensure local config is ignored, so results are consistent
if (!Log._skipLocalConfig) {
yield Log._loadLocalConfig();
}
});
}
/**
* Generate LoggingConfig for output (e.g. to stringify to JSON file)
* @static
* @method Log.getConfig
* @return {LoggingConfig}
*/
static getConfig() {
const logUncaughtExceptions = Log._globalErrorHandler.logUncaughtExceptions;
const logUnhandledRejections = Log._globalErrorHandler.logUnhandledRejections;
const namespaces = Object.keys(Log._outputPerNamespace).reduce((namespaces, namespace) => {
return Object.assign({}, namespaces, { [namespace]: Log._outputPerNamespace[namespace] });
}, {});
const outputs = Object.keys(Log._outputHandlers).reduce((outputs, name) => {
return Object.assign({}, outputs, { [name]: Log._outputHandlers[name].config });
}, {});
// Don't save out the skipLocalConfig value - it's just for tests
const stackTraceLimit = Error.stackTraceLimit;
const staticNamespaceLevels = Log._staticNamespaceLevels;
return {
logUncaughtExceptions,
logUnhandledRejections,
namespaces,
outputs,
stackTraceLimit,
staticNamespaceLevels,
};
}
/**
* Save the current log configuration file to a local logging config file
* If process.env.JIBO_LOG_CONFIG: use that; else...
* On-robot: /var/jibo/t.logging.json
* Off-robot: ~/.jibo/t.logging.json
* @static
* @method Log.saveConfig
* @return {Promise<void>}
*/
static saveConfig() {
return __awaiter(this, void 0, void 0, function* () {
try {
yield prify((cb) => {
fsx.ensureDir(path.dirname(Log._localConfigPath), cb);
});
}
catch (err) {
Log._jiboLog.warn(`Error creating directory for ${Log._localConfigPath}`, err);
return;
}
try {
const stringConfig = JSON.stringify(Log.getConfig(), null, 4);
yield prify((cb) => {
fs.writeFile(Log._localConfigPath, stringConfig, cb);
});
}
catch (err) {
Log._jiboLog.warn(`Error writing local config to ${Log._localConfigPath}`, err);
return;
}
Log._jiboLog.info(`Wrote local config to ${Log._localConfigPath}`);
});
}
/**
* Set up listener for log-level update notifications
* @static
* @method Log.handleLogLevelNotifications
* @param {events.EventEmitter} notificationsDispatcher Event emitter that emits 'LevelChanged' events
* @param {boolean} [save = false] Whether to save the configuration to disk or not
* @return {Promise<void>}
*/
static handleLogLevelNotifications(notificationsDispatcher,
// This should only be set true when called from the SSM process, to
// ensure that the change is only written to disk once
save = false) {
return __awaiter(this, void 0, void 0, function* () {
Log._jiboLog.info('Registering for log "LevelChanged" notifications');
notificationsDispatcher.on('LevelChanged', (changes) => {
Log._jiboLog.info('Got a log "LevelChanged" notification:', changes);
changes
.filter((change) => change.namespace.startsWith('T.'))
.forEach((change) => {
const namespace = change.namespace.slice(2);
const level = (['verbose', 'silly'].includes(change.level)
? 'debug'
: change.level);
Log._jiboLog.info(`Setting ${namespace} to ${level}`);
const log = new Log(namespace);
log.outputs.syslog = level;
});
if (save) {
Log._jiboLog.info('Saving config to disk');
Log.saveConfig();
}
});
});
}
/**
* Flush all log lines that output channels may have cached in memory
* @static
* @method Log.flush
* @return {void}
*/
static flush() {
Object.keys(Log._outputHandlers)
.map((output) => Log._outputHandlers[output])
.forEach((handler) => handler && handler.flush());
}
static get _onRobot() {
// Check environment variables for run mode
let runMode = process.env.runMode || process.env.RUNMODE;
// Default to ON_ROBOT under arm/linux if not set
if (!runMode
&& process.platform === 'linux' && process.arch === 'arm') {
runMode = 'ON_ROBOT';
}
return runMode === 'ON_ROBOT';
}
static _loadConfig(loggingConfig) {
// Set this first, since it affects behavior later
if (loggingConfig.staticNamespaceLevels !== undefined) {
Log._staticNamespaceLevels = loggingConfig.staticNamespaceLevels;
}
if (loggingConfig.logUncaughtExceptions !== undefined) {
Log._globalErrorHandler.logUncaughtExceptions =
loggingConfig.logUncaughtExceptions;
}
if (loggingConfig.logUnhandledRejections !== undefined) {
Log._globalErrorHandler.logUnhandledRejections =
loggingConfig.logUnhandledRejections;
}
if (loggingConfig.namespaces) {
const outputPerNamespaceIn = loggingConfig.namespaces;
Object.keys(outputPerNamespaceIn).forEach((namespace) => {
const levelPerOutput = Log._outputPerNamespace[namespace] = {};
const levelPerOutputIn = outputPerNamespaceIn[namespace];
Object.keys(levelPerOutputIn).forEach((output) => {
const levelIn = levelPerOutputIn[output];
const level = levelIn;
levelPerOutput[output] = level;
});
});
// Fix the outputPerNamespace for the global logger
if (!Log._staticNamespaceLevels) {
Log.global.outputPerNamespace = Log._outputPerNamespace;
}
}
if (loggingConfig.outputs) {
const outputsIn = loggingConfig.outputs;
Object.keys(outputsIn).forEach((output) => {
if (!this._outputHandlers[output]) {
console.error(`Missing output ${output}`);
}
else {
this._outputHandlers[output].config = outputsIn[output];
}
});
}
if (loggingConfig.skipLocalConfig !== undefined) {
Log._skipLocalConfig = loggingConfig.skipLocalConfig;
}
if (loggingConfig.stackTraceLimit > -1) {
Error.stackTraceLimit = loggingConfig.stackTraceLimit;
}
}
static _loadLocalConfig() {
return __awaiter(this, void 0, void 0, function* () {
// If we've already loaded up a local config file, just load it again
if (Log._localConfig) {
return Log._loadConfig(Log._localConfig);
}
// Otherwise, only try looking for a new file if we're not initialized
// This way, if there's no file, we don't do these checks when a
// process loads its own configuration
if (Log._initialized) {
return;
}
if (Log._onRobot) {
yield Log._loadLocalConfigOnRobot();
}
else {
yield Log._loadLocalConfigOffRobot();
}
});
}
// Don't create a default config file; only load it if it was
// manually placed there by an internal robot developer
static _loadLocalConfigOnRobot() {
return __awaiter(this, void 0, void 0, function* () {
if (yield prify((cb) => {
fs.exists(Log._localConfigPath, cb);
}, false)) {
try {
Log._localConfig = JSON.parse(yield prify((cb) => {
fs.readFile(Log._localConfigPath, 'utf-8', cb);
}));
Log._loadConfig(Log._localConfig);
Log._jiboLog.debug(`Loaded local config from ${Log._localConfigPath}`);
}
catch (err) {
Log._jiboLog.warn(`Could not load logging config file ${Log._localConfigPath}`, err);
}
}
});
}
// If not on a robot, read a user config file from their home/.jibo
// directory; place one there if there isn't already one there, for
// convenience
static _loadLocalConfigOffRobot() {
return __awaiter(this, void 0, void 0, function* () {
// If there's a config file, load it up
if (yield prify((cb) => {
fs.exists(Log._localConfigPath, cb);
}, false)) {
try {
Log._loadConfig(JSON.parse(yield prify((cb) => {
fs.readFile(Log._localConfigPath, 'utf-8', cb);
})));
Log._jiboLog.debug(`Loaded local config from ${Log._localConfigPath}`);
}
catch (err) {
Log._jiboLog.warn(`Could not load logging config file ${Log._localConfigPath}`, err);
}
}
else {
yield Log.saveConfig();
}
});
}
// Chop off the last part of the namespace, including its delimiter
static _parentNamespace(namespace) {
const lastSlash = namespace.lastIndexOf(NAMESPACE_DELIMITER);
return lastSlash > -1
? namespace.slice(0, lastSlash)
: '';
}
/**
* Whether or not to log globally uncaught exceptions
* @static
* @name Log.logUncaughtExceptions
* @type {boolean}
*/
static set logUncaughtExceptions(log) {
Log._globalErrorHandler.logUncaughtExceptions = log;
}
static get logUncaughtExceptions() {
return Log._globalErrorHandler.logUncaughtExceptions;
}
/**
* Whether or not to log globally unhandled promise rejections
* @static
* @name Log.logUnhandledRejections
* @type {boolean}
*/
static set logUnhandledRejections(log) {
Log._globalErrorHandler.logUnhandledRejections = log;
}
static get logUnhandledRejections() {
return Log._globalErrorHandler.logUnhandledRejections;
}
// Check to see if the first level is greater than or equal to the second.
static _levelGreaterOrEqual(levelOne, levelTwo) {
return Level_1.levelOrder.indexOf(levelOne) >= Level_1.levelOrder.indexOf(levelTwo);
}
/**
* A reference to the top level logger instance with namespace ''
* @name Log#global
* @type {Log}
*/
get global() { return Log.global; }
/**
* Configuration for the output of this logger
* @readonly
* @name Log#outputs
* @type {LogOutputs}
*/
get outputs() {
return new LogOutputs_1.default(this);
}
/**
* Creates a new Log instance with namespace `this.namespace + '/' +
* subNamespace`
* @method Log#createChild
* @param {string} subNamespace New namespace which will be appended to
* the parent namespace
* @return {Log}
*/
createChild(subNamespace) {
const log = new Log(subNamespace, this);
log.robotID = this.robotID;
log.transID = this.transID;
return log;
}
set outputPerNamespace(outputPerNamespaceIn) {
if (outputPerNamespaceIn) {
Object.keys(outputPerNamespaceIn).forEach((namespace) => {
const outputPerNamespace = Log._staticNamespaceLevels
? Log._outputPerNamespace
: this._outputPerNamespace;
const levelPerOutput = outputPerNamespace[namespace] = {};
const levelPerOutputIn = outputPerNamespaceIn[namespace];
Object.keys(levelPerOutputIn).forEach((output) => {
levelPerOutput[output] = levelPerOutputIn[output];
});
});
}
}
/**
* Set the minimum logging level for a given output for this namespace
* This is generally not invoked directly, but by LogOutputs by setting
* one of the properties of the outputs property of a log instance
* e.g. `log.outputs.console = 'debug';`
* @method Log#setLevelForOutput
* @param {Output} output The output for which to set the level
* @param {Level} level The level to which to set the output
* @return {void}
*/
setLevelForOutput(output, level) {
const outputPerNamespace = Log._staticNamespaceLevels
? Log._outputPerNamespace
: this._outputPerNamespace;
if (!outputPerNamespace[this.namespace]) {
outputPerNamespace[this.namespace] = {};
}
outputPerNamespace[this.namespace][output.toString()] = level;
}
/**
* Check minimum logging level for each output channel for this log's
* namespace, and log the message if this level is debug or lower
* @method Log#debug
* @param {any[]} ...args The arguments to log
* @return {Log} Instance of log for chaining
*/
debug(...args) {
return this._log(types_1.Level.debug, ...args);
}
/**
* Check minimum logging level for each output channel for this log's
* namespace, and log the message if this level is info or lower
* @method Log#info
* @param {any[]} ...args The arguments to log
* @return {Log} Instance of log for chaining
*/
info(...args) {
return this._log(types_1.Level.info, ...args);
}
/**
* Check minimum logging level for each output channel for this log's
* namespace, and log the message if this level is warn or lower
* @method Log#warn
* @param {any[]} ...args The arguments to log
* @return {Log} Instance of log for chaining
*/
warn(...args) {
return this._log(types_1.Level.warn, ...args);
}
/**
* Check minimum logging level for each output channel for this log's
* namespace, and log the message if this level isn't none
* @method Log#error
* @param {any[]} ...args The arguments to log
* @return {Log} Instance of log for chaining
*/
error(...args) {
return this._log(types_1.Level.error, ...args);
}
/**
* A synonym for Log#debug
* @method Log#log
* @param {any[]} ...args The arguments to log
* @return {Log} Instance of log for chaining
*/
log(...args) {
this.debug(...args);
return this;
}
/**
* Log the error if it is set
* @method Log#iferr
* @param {Error|String} err The error which is checked and logged if
* truthy
* @param {any[]} [...args] Additional arguments to log along with err
* @return {Log} Instance of log for chaining
*/
iferr(err, ...args) {
if (err) {
args.push(err); // add the error to the end
this.error(...args);
}
return this;
}
/**
* Get the minimum logging level for the given output for this namespace.
* If a level isn't set for the namespace, it checks its parent namespace
* and so on until it finds one, or uses the global namespace.
* @method Log#getLevelForOutput
* @param {Output} output The output channel for which to get the level
*/
getLevelForOutput(output) {
let namespace = this.namespace;
const outputPerNamespace = Log._staticNamespaceLevels
? Log._outputPerNamespace
: this._outputPerNamespace;
let config = outputPerNamespace[namespace];
while (!(config && config[output.toString()])) {
if (!namespace || namespace.length < 1) {
break;
}
namespace = Log._parentNamespace(namespace);
config = outputPerNamespace[namespace];
}
return config && config[output.toString()]
? config[output.toString()]
: types_1.Level.none;
}
// Check minimum logging level for each output channel for this log's
// namespace, and log the message if the passed-in level is greater than or
// equal to the minimum logging level for the output
_log(level, ...args) {
Object.keys(Log._outputHandlers).forEach((output) => {
if (Log._levelGreaterOrEqual(level, this.getLevelForOutput(types_1.Output[output]))) {
Log._outputHandlers[output].write(new Date(), this.robotID, this.transID, Log.processName, Log.release, this.namespace ? `${Log.topLevelNamespace}.${this.namespace}` : Log.topLevelNamespace, level, ...args);
}
});
return this;
}
}
/**
* Current version of the library.
* @static
* @readonly
* @name Log.version
* @type {string}
*/
Log.version = '5.0.6';
/**
* The global logger, with empty namespace
* @static
* @readonly
* @name Log.global
* @type {string}
*/
Log.global = new Log();
/**
* Current version of the Jibo platform release.
* Can be overridden, for instance to be the version of a cloud skill
* instead of a robot
* @static
* @name Log.release
* @type {string}
*/
Log.release = getPlatformVersion_1.default().version;
/**
* Name of the currently running process, sent to syslog in the ProcessName
* header; should be set by each process during its initialization
* @static
* @name Log.processName
* @type {string}
*/
Log.processName = process.argv0;
/**
* The top-level namespace, added to the front of the namespace in
* syslog and file output; should be set by each process during its
* initialization
* @static
* @name Log.topLevelNamespace
* @type {string}
*/
Log.topLevelNamespace = 'T';
// Local logger for use only from this class
Log._jiboLog = new Log('Jibo.Log');
// Whether or not the default logging config has been loaded
Log._initialized = false;
// Whether or not to skip loading local logging configuration (for tests)
Log._skipLocalConfig = false;
// Whether to keep output levels per namespace statically or per instance
Log._staticNamespaceLevels = true;
// Global logging configuration, usually loaded from JSON
// Holds the minimum logging levels per output, per namespace
Log._outputPerNamespace = {};
// A special logger just for unhandled exceptions
Log._unhandledExceptionLogger = new Log('Unhandled');
Log._uncaughtMap = {};
// Global error handler, for globally uncaught exceptions and globally
// unhandled rejections
Log._globalErrorHandler = new utils_1.GlobalErrorHandler((...args) => {
const message = args[0] instanceof Error ? args[0].message : args[0];
let firstLog = false;
if (Log._uncaughtMap[message] === undefined) {
firstLog = true;
Log._uncaughtMap[message] = Date.now();
}
// //only log every 5 minutes if happening at 30fps
if (Date.now() - Log._uncaughtMap[message] > 60000 || firstLog) {
Log._unhandledExceptionLogger.error(...args);
Log._uncaughtMap[message] = Date.now();
}
});
Log.formatItem = utils_1.formatItem;
// These are here for testing purposes only
Log.ConsoleHandler = output_handlers_1.ConsoleHandler;
//static FileHandler = FileHandler;
Log.SyslogHandler = output_handlers_1.SyslogHandler;
exports.default = Log;
},{"./LogOutputs":2,"./defaultConfig":4,"./output-handlers":9,"./types":12,"./types/Level":10,"./utils":18,"./utils/PromiseUtils":14,"./utils/getPlatformVersion":16,"fs":undefined,"fs-extra":undefined,"os":undefined,"os-homedir":undefined,"path":undefined}],2:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("./types");
/**
* LogOutputs class - logging level for each output channel
* @class LogOutputs
* @param {Log} [_log] The log on which this is the outputs property
* @param {string} [_namespace?] Defaults to the log's namespace, but overridable
*/
class LogOutputs {
constructor(_log) {
this._log = _log;
if (!this._log) {
throw new Error('log is a required parameter');
}
}
/**
* Set the logging level for all output channels at once
* @name LogOutputs#all
* @type {Level}
*/
set all(level) {
this.console = level;
this.file = level;
this.pegasus = level;
this.syslog = level;
}
/**
* The logging level for the console output channel
* @name LogOutputs#console
* @type {Level}
*/
set console(level) {
this._log.setLevelForOutput(types_1.Output.console, level);
}
get console() {
return this._log.getLevelForOutput(types_1.Output.console);
}
/**
* The logging level for the file output channel
* @name LogOutputs#file
* @type {Level}
*/
set file(level) {
this._log.setLevelForOutput(types_1.Output.file, level);
}
get file() {
return this._log.getLevelForOutput(types_1.Output.file);
}
/**
* The logging level for the Pegasus output channel
* @name LogOutputs#pegasus
* @type {Level}
*/
set pegasus(level) {
this._log.setLevelForOutput(types_1.Output.pegasus, level);
}
get pegasus() {
return this._log.getLevelForOutput(types_1.Output.pegasus);
}
/**
* The logging level for the syslog output channel
* @name LogOutputs#syslog
* @type {Level}
*/
set syslog(level) {
this._log.setLevelForOutput(types_1.Output.syslog, level);
}
get syslog() {
return this._log.getLevelForOutput(types_1.Output.syslog);
}
}
exports.default = LogOutputs;
},{"./types":12}],3:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
global.Singletons = global.Singletons || {};
class Singletons {
static enforce(candidate) {
let singleton = candidate;
let singletonName = candidate.name;
if (!global.Singletons[singletonName]) {
global.Singletons[singletonName] = singleton;
}
else {
singleton = global.Singletons[singletonName];
// let's only warn if the versions don't match
// console.warn(
// 'Singletons: using existing singleton',
// singletonName
// );
// optionally check if versions match (and warn if not)
if (candidate.version) {
let candidateVersion = candidate.version;
let singletonVersion = singleton.version;
if (typeof candidate.version === 'function') {
candidateVersion = candidate.version();
}
if (typeof singleton.version === 'function') {
singletonVersion = singleton.version();
}
if (candidateVersion !== singletonVersion) {
console.log(singletonName, 'singleton: existing version', singletonVersion, 'does not equal candidate version', candidateVersion);
}
}
}
return singleton;
}
}
exports.default = Singletons;
},{}],4:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("./types");
exports.default = {
staticNamespaceLevels: true,
logUncaughtExceptions: true,
logUnhandledRejections: true,
stackTraceLimit: 30,
skipLocalConfig: false,
outputs: {
console: {
outputFileAndLine: false,
outputLevel: false,
},
syslog: {
port: 514,
target: "127.0.0.1",
outputFileAndLine: false,
}
},
namespaces: {
'': {
console: types_1.Level.info,
file: types_1.Level.none,
syslog: types_1.Level.info,
},
},
};
},{"./types":12}],5:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const types_1 = require("../types");
const OutputHandler_1 = require("./OutputHandler");
/**
* Output channel that logs to global.console or equivalent interface
* @class ConsoleHandler
* @extends OutputHandler
* @param {Console} [console = global.console] You may pass a alternative console
*/
class ConsoleHandler extends OutputHandler_1.default {
constructor(_console = global.console) {
super();
this._console = _console;
}
write(timestamp, robotID, transID, processName, release, namespace, level, ...args) {
const stringArgs = [];
// Place the log-level in front of everything, if required
if (this._config && this._config.outputLevel) {
stringArgs.push(`[${level}]`);
}
if (this._config && this._config.outputRobotAndTransIDs) {
stringArgs.push(robotID || '-');
stringArgs.push(transID || '-');
}
stringArgs.push(namespace);
// If the first argument passed by the code author is a string,
// include it in the paramenters to splice together into a single arg
const firstArgString = args.length && typeof args[0] === 'string';
if (firstArgString) {
stringArgs.push(args[0]);
}
const logArgs = [
// Splice the string arguments in front into a single item, to
// allow for the Console to honor string substitutions
// https://developer.mozilla.org/en-US/docs/Web/API/console#Outputting_text_to_the_console
stringArgs.join(' '),
// If the first argument is a string, it's already been included
...(firstArgString ? args.slice(1) : args),
// Add the output filename and line to the end, if configured
...(this._config && this._config.outputFileAndLine
// The first four items on the stack are from jibo-log:
// 1. Log#debug/info/warn/error
// 2. Log#_log
// 3. forEach(output: OutputHandler)
// 4. OutputHandler#write
? [new Error().stack.split('\n')[4]]
: []),
];
switch (level) {
case types_1.Level.debug:
this._console.log(...logArgs);
break;
case types_1.Level.info:
this._console.info(...logArgs);
break;
case types_1.Level.warn:
this._console.warn(...logArgs);
break;
case types_1.Level.error:
this._console.error(...logArgs);
break;
}
}
}
exports.default = ConsoleHandler;
},{"../types":12,"./OutputHandler":7}],6:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const osHomedir = require("os-homedir");
const OutputHandler_1 = require("./OutputHandler");
const path = require("path");
const RFS = require("rotating-file-stream");
const DEFAULT_CONFIG = Object.freeze({
filename: null,
size: '10M',
compress: true,
outputFileAndLine: false,
});
/**
* Output channel that logs to the filesystem
* @class FileHandler
*/
class FileHandler extends OutputHandler_1.default {
constructor() {
super();
// If FileConfig ever goes below depth one, this will need to be
// changed to a deep copy (lodash.assign)
this._config = Object.freeze(Object.assign({}, DEFAULT_CONFIG));
}
write(time, robotID, transID, processName, release, namespace, level, ...args) {
// only write to file if the writable stream is initialized
if (this._stream) {
const timestamp = time.toISOString();
const items = [
...args.map(item => utils_1.formatItem(item)),
...(this._config.outputFileAndLine
? [new Error().stack.split('\n')[4]]
: []),
];
const message = `${namespace}: ${items.join(' ')}`;
const versions = `[versions@1 release="${release}"]`;
const transaction = transID ? `[messageContext@1 transID="${transID}"]` : '';
const structuredData = `${versions}${transaction}`;
const fullText = `${timestamp} ${robotID || '-'} ${processName}[${process.pid},${level}]: ${structuredData || '-'} ${message}\n`;
this._stream._write(fullText, 'utf-8', console.error);
}
}
/**
* The filesystem output configuration for this handler
* @name FileHandler#config
* @type {FileConfig}
*/
set config(config) {
const oldConfig = this._config;
this._config = Object.freeze(Object.assign({}, this._config, config));
if (this._stream) {
this._stream.end();
}
if (config.filename && oldConfig.filename !== config.filename) {
if (config.filename.length > 0) {
try {
this._createStream();
}
catch (err) {
console.warn('Error opening log file for output,', err);
}
}
else {
// rotating-file-stream doesn't provide a way to close the file
// lame.
this._stream = null;
}
}
}
get config() {
return this._config;
}
_createStream() {
const filePath = path.resolve(this._config.filename.startsWith('~/')
? path.join(osHomedir(), this._config.filename.slice(2))
: this._config.filename);
const options = {};
if (this._config.size) {
options.size = this._config.size;
}
if (this._config.interval) {
options.interval = this._config.interval;
}
if (this._config.compress) {
options.compress = 'gzip';
}
this._stream = new RFS(filePath, options);
}
}
exports.default = FileHandler;
},{"../utils":18,"./OutputHandler":7,"os-homedir":undefined,"path":undefined,"rotating-file-stream":undefined}],7:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* OutputHandler class - logging to global.console
* @abstract
* @class ConsoleHandler
* @extends OutputHandler
*/
class OutputHandler {
/**
* The output configuration for this handler
* @name OutputHandler#config
* @type {Config}
*/
set config(config) {
this._config = config;
}
get config() {
return this._config;
}
/**
* Flush any log lines this handler caches; default to no behavior
* @return {Promise<void>} Resolves when the flush is complete
*/
flush() {
return Promise.resolve();
}
}
exports.default = OutputHandler;
},{}],8:[function(require,module,exports){
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("../utils");
const types_1 = require("../types");
const OutputHandler_1 = require("./OutputHandler");
const Syslog = require("syslog2-pure-js");
// Default configuration
const DEFAULT_CONFIG = Object.freeze({
port: 514,
target: '127.0.0.1',
outputFileAndLine: false,
});
const FACILITY = 'local0';
/**
* Output channel that logs to syslog (/var/log/messages)
* Only enabled on linux/arm (e.g. Jibo robot)
* @class SyslogHandler
*/
class SyslogHandler extends OutputHandler_1.default {
static _toStructuredData(err) {
return Object.getOwnPropertyNames(err).reduce((obj, key) => Object.assign({}, obj, {
[key === 'stack' ? 'frames' : key]: (key === 'stack'
? JSON.stringify(SyslogHandler._getStackFrames(err))
: err[key])
}), {});
}
static _getStackFrames(err) {
return err.stack
.split('\n').slice(1)
.map(str => str
.replace(/^ at /, '')
.replace(/\)$/, '')
.split(' ('))
.map(frame => frame.length > 1 ? frame : ['', ...frame])
.map(frame => [frame[0], ...frame[1].split(':')])
.map(frame => {
return {
method: frame[0],
filename: frame[1],
line: parseInt(frame[2]),
column: parseInt(frame[3]),
};
});
}
constructor() {
super();
// don't bother on anything other than our linux/arm environment
this._enabled = process.platform === 'linux' && process.arch === 'arm';
// If SyslogConfig ever goes below depth one, this will need to be
// changed to a deep copy (lodash.assign)
this._config = Object.freeze(Object.assign({}, DEFAULT_CONFIG));
}
write(date, robotID, transID, processName, release, namespace, level, ...args) {
return __awaiter(this, void 0, void 0, function* () {
// only write to syslog if it's enabled
if (this._enabled) {
// Create a client if there isn't one already
if (!this._client) {
try {
yield this._createClient();
}
catch (err) {
return console.warn('Error connecting to syslog', err);
}
}
const appName = processName;
const errors = {};
// Pull errors out of message and into structured data with
// structured stack traces
const unformattedItems = args.reduce((items, item, index) => {
if (item instanceof Error) {
errors[index] =
SyslogHandler._toStructuredData(item);
// Add the error message to the log-message body
return [...items, item.message];
}
else {
// Add the entire non-Error item to the log-message body
return [...items, item];
}
}, []);
// Format items and add file and line, if required
const logArgs = unformattedItems.map(item => utils_1.formatItem(item));
if (this._config && this._config.outputFileAndLine) {
// The first four items on the stack are from jibo-log:
// 1. Log#debug/info/warn/error
// 2. Log#_log
// 3. forEach(output: OutputHandler)
// 4. OutputHandler#write
logArgs.push(new Error().stack.split('\n')[4]);
}
const message = `${namespace}: ${logArgs.join(' ')}`;
const severity = level === types_1.Level.error
? 'ERR'
: level.toString().toUpperCase();
// Build up the structured data object
const structuredData = Object.assign({}, (Object.keys(errors).length ? errors : {}), { versions: { release } }, (transID ? { messageContext: { transID } } : {}));
// Create a Glossy record for output
const record = { appName, date, message, severity, structuredData };
// Write it out to the syslog daemon
this._client.write(record);
}
});
}
/**
* The syslog output configuration for this handler
* @name SyslogHandler#config
* @type {SyslogConfig}
*/
set config(config) {
this._config = Object.freeze(Object.assign({}, this._config, config));
}
get config() {
return this._config;
}
// Create a syslog client on the configured port and IP/hostname
_createClient() {
return new Promise((resolve, reject) => {
this._client = Syslog.create({
connection: {
host: this._config.target,
port: this._config.port,
},
facility: FACILITY,
PEN: 1,
useStructuredData: true,
}, err => {
if (err) {
this._client = undefined;
reject(err);
}
else {
resolve();
}
});
});
}
}
exports.default = SyslogHandler;
},{"../types":12,"../utils":18,"./OutputHandler":7,"syslog2-pure-js":undefined}],9:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const ConsoleHandler_1 = require("./ConsoleHandler");
exports.ConsoleHandler = ConsoleHandler_1.default;
const FileHandler_1 = require("./FileHandler");
exports.FileHandler = FileHandler_1.default;
const OutputHandler_1 = require("./OutputHandler");
exports.OutputHandler = OutputHandler_1.default;
const SyslogHandler_1 = require("./SyslogHandler");
exports.SyslogHandler = SyslogHandler_1.default;
const types_1 = require("../types");
const consoleHandler = new ConsoleHandler_1.default();
const fileHandler = new FileHandler_1.default();
const syslogHandler = new SyslogHandler_1.default();
exports.outputHandlers = {
[types_1.Output.console]: consoleHandler,
[types_1.Output.file]: fileHandler,
[types_1.Output.syslog]: syslogHandler,
};
},{"../types":12,"./ConsoleHandler":5,"./FileHandler":6,"./OutputHandler":7,"./SyslogHandler":8}],10:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* A level of logging output, debug/info/warn/error, or none
* @typedef Level
*/
var Level;
(function (Level) {
Level["none"] = "none";
Level["debug"] = "debug";
Level["info"] = "info";
Level["warn"] = "warn";
Level["error"] = "error";
})(Level || (Level = {}));
exports.default = Level;
exports.levelOrder = [
Level.debug,
Level.info,
Level.warn,
Level.error,
Level.none,
];
},{}],11:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* A logging output channel, console/syslog/file
* @name Output
* @type enum
* @prop console 'console'
* @prop syslog 'syslog'
* @prop file 'file'
*/
var Output;
(function (Output) {
Output["console"] = "console";
Output["file"] = "file";
Output["pegasus"] = "pegasus";
Output["syslog"] = "syslog";
})(Output || (Output = {}));
exports.default = Output;
},{}],12:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const Level_1 = require("./Level");
exports.Level = Level_1.default;
const Output_1 = require("./Output");
exports.Output = Output_1.default;
},{"./Level":10,"./Output":11}],13:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const stream = require("stream");
class NullStream extends stream.Writable {
constructor(opts = {}) {
opts.objectMode = true;
super(opts);
}
_write(chunk, encoding, cb) {
cb();
return true;
}
}
exports.default = NullStream;
},{"stream":undefined}],14:[function(require,module,exports){
"use strict";
/**
* @fileOverview
*
* Created on 5/12/16.
* @author Siggi Orn <siggi@jibo.com>
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
class PromiseUtils {
/**
* Returns promise that succeeds when the first input promise succeeds
* If all input promises fail, then the returned promise fails.
* @param {Promise<T>[]} promises
* @returns {Promise<T>}
*/
static firstToSucceed(promises) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((res, rej) => {
let errors = [];
let errorCount = 0;
for (let i = 0; i < promises.length; i++) {
promises[i].then(res).catch(e => {
errors[i] = e;
errorCount++;
if (errorCount === promises.length) {
rej(errors);
}
});
}
});
});
}
static promisify(func, firstParamError = true) {
return new Promise((resolve, reject) => {
func((error, result) => {
if (firstParamError) {
if (error) {
reject(error);
}
else {
resolve(result);
}
}
else {
resolve(error);
}
});
});
}
}
exports.PromiseUtils = PromiseUtils;
},{}],15:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const util = require("util");
const format_error_1 = require("format-error");
// Format the passed object for string output
const FORMAT_ERROR_OPTIONS = {
noColor: true
};
const INSPECT_OPTIONS = {
depth: 3,
maxArrayLength: 10,
};
function _formatError(error) {
return _formatObject(format_error_1.format(error, FORMAT_ERROR_OPTIONS));
}
function _formatObject(obj) {
return util.inspect(obj, INSPECT_OPTIONS);
}
/**
* Format the passed item as a single-line string for logging (i.e. syslog)
* @function formatItem
* @param args {any[]} Item to format
* @return {string} Formatted line, ready for logging
*/
function formatItem(item) {
// If the object has a method specifically for logging usage, then use that
// instead of the original
const preformatted = item && typeof item.toLog === 'function' ? item.toLog() : item;
const message = typeof preformatted === 'string' ? preformatted.trim() :
preformatted instanceof Error ? _formatError(preformatted) :
_formatObject(preformatted);
return message
.replace(/\\n/g, '\\\\n')
.replace(/\r?\n/g, '\\n');
}
exports.formatItem = formatItem;
},{"format-error":undefined,"util":undefined}],16:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function getPlatformVersion() {
// Get the platform build version
let tbd;
try {
tbd = require('/opt/jibo/Jibo/Skills/jibo-tbd/package.json');
}
catch (err) {
tbd = { version: '8.67.5309' };
}
return tbd;
}
exports.default = getPlatformVersion;
},{"/opt/jibo/Jibo/Skills/jibo-tbd/package.json":undefined}],17:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Utility class to handle globally-uncaught exceptions and globally-unhandled
* promise rejections
* @class GlobalErrorHandler
* @param {string|Error} handleError Callback where errors are sent
*/
class GlobalErrorHandler {
constructor(handleError) {
this._handleError = handleError;
}
// Log uncaught exceptions caught by process
_uncaughtExceptionProcessLogger(error) {
this._handleError(error);
}
// Log uncaught exceptions caught by window
_uncaughtExceptionWindowLogger(message, filename, lineno, colno, error) {
this._handleError(`${message}, source: ${filename}, line: ${lineno}, column: ${colno}`, error);
}
/**
* Whether or not to log globally uncaught exceptions
* @name GlobalErrorHandler.logUncaughtExceptions
* @type {boolean}
*/
set logUncaughtExceptions(log) {
if (log && !this._loggingUncaughtExceptions) {
if (typeof window !== 'undefined') {
window.onerror = this._uncaughtExceptionWindowLogger.bind(this);
}
else {
process.on('uncaughtException', this._uncaughtExceptionProcessLogger.bind(this));
}
}
else if (!log && this._loggingUncaughtExceptions) {
if (typeof window !== 'undefined') {
window.onerror = null;
}
else {
process.removeListener('uncaughtException', this._uncaughtExceptionProcessLogger);
}
}
this._loggingUncaughtExceptions = log;
}
get logUncaughtExceptions() {
return this._loggingUncaughtExceptions;
}
// Log unhandled promise rejections caught by process
_unhandledRejectionProcessLogger(error) {
this._handleError(error);
}
// In electron, unhandledrejection event is on window, not process, and
// the callback signature is different
_unhandledRejectionWindowLogger(event) {
this._handleError(event.reason);
}
/**
* Whether or not to log globally unhandled promise rejections
* @name GlobalErrorHandler.logUnhandledRejections
* @type {boolean}
*/
set logUnhandledRejections(log) {
if (log && !this._loggingUnhandledRejections) {
if (typeof window !== 'undefined') {
window.onunhandledrejection =
this._unhandledRejectionWindowLogger.bind(this);
}
else {
process.on('unhandledRejection', this._unhandledRejectionProcessLogger.bind(this));
}
}
else if (!log && this._loggingUnhandledRejections) {
if (typeof window !== 'undefined') {
window.onunhandledrejection = null;
}
else {
process.removeListener('unhandledRejection', this._unhandledRejectionProcessLogger);
}
}
this._loggingUnhandledRejections = log;
}
get logUnhandledRejections() {
return this._loggingUnhandledRejections;
}
}
exports.GlobalErrorHandler = GlobalErrorHandler;
},{}],18:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const formatting_1 = require("./formatting");
exports.formatItem = formatting_1.formatItem;
const global_errors_1 = require("./global-errors");
exports.GlobalErrorHandler = global_errors_1.GlobalErrorHandler;
const NullStream_1 = require("./NullStream");
exports.NullStream = NullStream_1.default;
const PromiseUtils_1 = require("./PromiseUtils");
exports.PromiseUtils = PromiseUtils_1.PromiseUtils;
},{"./NullStream":13,"./PromiseUtils":14,"./formatting":15,"./global-errors":17}],19:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const output_handlers_1 = require("./output-handlers");
exports.OutputHandler = output_handlers_1.OutputHandler;
const types_1 = require("./types");
exports.Level = types_1.Level;
exports.Output = types_1.Output;
const Log_1 = require("./Log");
const LogOutputs_1 = require("./LogOutputs");
exports.LogOutputs = LogOutputs_1.default;
const Singletons_1 = require("./Singletons");
const Log = Singletons_1.default.enforce(Log_1.default);
exports.Log = Log;
Log.initialize();
},{"./Log":1,"./LogOutputs":2,"./Singletons":3,"./output-handlers":9,"./types":12}]},{},[19])(19)
});
//# sourceMappingURL=jibo-log.js.map