(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} */ 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} 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} */ // 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} */ 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} */ 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} 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 */ 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[]} promises * @returns {Promise} */ 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