diff --git a/client/src/app/base-mvc.js b/client/src/app/base-mvc.js index 961a02ff590..597df26c0bc 100644 --- a/client/src/app/base-mvc.js +++ b/client/src/app/base-mvc.js @@ -1,41 +1,8 @@ import $ from "jquery"; import _ from "underscore"; import Backbone from "backbone"; -import addLogging from "utils/add-logging"; import _l from "utils/localization"; -//============================================================================== -/** @class Mixin to add logging capabilities to an object. - * Designed to allow switching an objects log output off/on at one central - * statement. Can be used with plain browser console (or something more - * complex like an AJAX logger). - *
NOTE: currently only uses the console.debug log function - * (as opposed to debug, error, warn, etc.) - * @name LoggableMixin - * - * @example - * // Add to your models/views at the definition using chaining: - * var MyModel = Backbone.Model.extend( LoggableMixin ).extend({ // ... }); - * - * // or - more explicitly AFTER the definition: - * var MyModel = Backbone.Model.extend({ - * logger : console - * // ... - * this.log( '$#%& it! - broken already...' ); - * }) - * _.extend( MyModel.prototype, LoggableMixin ) - * - */ -var LoggableMixin = /** @lends LoggableMixin# */ { - // replace null with console (if available) to see all logs for a particular view/model - /** The logging object whose log function will be used to output - * messages. Null will supress all logging. Commonly set to console. - */ - logger: null, - /** @type {String} a namespace for filtering/focusing log output */ - _logNamespace: ".", -}; -addLogging(LoggableMixin); //============================================================================== /** Backbone model that syncs to the browser's sessionStorage API. @@ -575,7 +542,6 @@ function buildComparator(attribute_name, options) { //============================================================================== export default { - LoggableMixin: LoggableMixin, SessionStorageModel: SessionStorageModel, mixin: mixin, SearchableModelMixin: SearchableModelMixin, diff --git a/client/src/app/galaxy.js b/client/src/app/galaxy.js index 0b506a86abf..a5430f34f0c 100644 --- a/client/src/app/galaxy.js +++ b/client/src/app/galaxy.js @@ -1,10 +1,7 @@ import $ from "jquery"; import _ from "underscore"; import Backbone from "backbone"; -import BASE_MVC from "./base-mvc"; import userModel from "./user-model"; -import metricsLogger from "utils/metrics-logger"; -import addLogging from "utils/add-logging"; import { localize, _setUserLocale, _getUserLocale } from "utils/localization"; import { getGalaxyInstance } from "app"; import { create, dialog } from "utils/data"; @@ -12,7 +9,6 @@ import { create, dialog } from "utils/data"; // ============================================================================ /** Base galaxy client-side application. * Iniitializes: - * logger : the logger/metrics-logger * localize : the string localizer * config : the current configuration (any k/v in * galaxy.ini available from the configuration API) @@ -22,29 +18,9 @@ export function GalaxyApp(options = {}, bootstrapped = {}) { this._init(options, bootstrapped); } -// add logging shortcuts for this object -addLogging(GalaxyApp, "GalaxyApp"); - -// a debug flag can be set via local storage and made available during script/page loading -const DEBUGGING_KEY = "galaxy:debug"; - -const NAMESPACE_KEY = `${DEBUGGING_KEY}:namespaces`; -const FLATTEN_LOG_MESSAGES_KEY = `${DEBUGGING_KEY}:flatten`; - -let localDebugging = false; -try { - localDebugging = localStorage.getItem(DEBUGGING_KEY) == "true"; -} catch (storageErr) { - console.log(localize("localStorage not available for debug flag retrieval")); -} - /** initalize options and sub-components */ GalaxyApp.prototype._init = function (options, bootstrapped) { _.extend(this, Backbone.Events); - if (localDebugging) { - this.logger = console; - console.debug("debugging galaxy:", "options:", options, "bootstrapped:", bootstrapped); - } this._processOptions(options); this._initConfig(options.config || {}); @@ -119,9 +95,6 @@ GalaxyApp.prototype._processOptions = function _processOptions(options) { GalaxyApp.prototype._initConfig = function _initConfig(config) { this.config = config; - // give precendence to localdebugging for this setting - this.config.debug = localDebugging || this.config.debug; - return this; }; @@ -141,41 +114,6 @@ GalaxyApp.prototype._patchGalaxy = function _patchGalaxy(patchWith) { } }; -/** set up the metrics logger (utils/metrics-logger) and pass loggerOptions */ -GalaxyApp.prototype._initLogger = function _initLogger(loggerOptions) { - // default to console logging at the debug level if the debug flag is set - if (this.config.debug) { - loggerOptions.consoleLogger = loggerOptions.consoleLogger || console; - loggerOptions.consoleLevel = loggerOptions.consoleLevel || metricsLogger.MetricsLogger.ALL; - // load any logging namespaces from localStorage if we can - try { - loggerOptions.consoleNamespaceWhitelist = localStorage.getItem(NAMESPACE_KEY).split(","); - } catch (storageErr) { - console.debug(storageErr); - } - try { - loggerOptions.consoleFlattenMessages = localStorage.getItem(FLATTEN_LOG_MESSAGES_KEY) == "true"; - } catch (storageErr) { - console.debug(storageErr); - } - console.log(loggerOptions.consoleFlattenMessages); - } - - this.logger = new metricsLogger.MetricsLogger(loggerOptions); - this.emit = {}; - ["log", "debug", "info", "warn", "error", "metric"].map((i) => { - this.emit[i] = (data) => { - this.logger.emit(i, arguments[0], Array.prototype.slice.call(arguments, 1)); - }; - }); - - if (this.config.debug) { - // add this logger to mvc's loggable mixin so that all models can use the logger - BASE_MVC.LoggableMixin.logger = this.logger; - } - return this; -}; - /** add the localize fn to this object and the window namespace (as '_l') */ GalaxyApp.prototype._initLocale = function _initLocale(options) { this.debug("_initLocale:", options); @@ -222,7 +160,6 @@ GalaxyApp.prototype._initUserLocale = function _initUserLocale(options) { GalaxyApp.prototype._initUser = function _initUser(userJSON) { this.debug("_initUser:", userJSON); this.user = new userModel.User(userJSON); - this.user.logger = this.logger; return this; }; @@ -248,51 +185,6 @@ GalaxyApp.prototype._setUpListeners = function _setUpListeners() { return this; }; -/** Turn debugging/console-output on/off by passing boolean. Pass nothing to get current setting. */ -GalaxyApp.prototype.debugging = function _debugging(setting) { - try { - if (setting === undefined) { - return localStorage.getItem(DEBUGGING_KEY) === "true"; - } - if (setting) { - localStorage.setItem(DEBUGGING_KEY, true); - return true; - } - - localStorage.removeItem(DEBUGGING_KEY); - // also remove all namespaces - this.debuggingNamespaces(null); - } catch (storageErr) { - console.log(localize("localStorage not available for debug flag retrieval")); - } - return false; -}; - -/** Add, remove, or clear namespaces from the debugging filters - * Pass no arguments to retrieve the existing namespaces as an array. - * Pass in null to clear all namespaces (all logging messages will show now). - * Pass in an array of strings or single string of the namespaces to filter to. - * Returns the new/current namespaces as an array; - */ -GalaxyApp.prototype.debuggingNamespaces = function _debuggingNamespaces(namespaces) { - try { - if (namespaces === undefined) { - const csv = localStorage.getItem(NAMESPACE_KEY); - return typeof csv === "string" ? csv.split(",") : []; - } else if (namespaces === null) { - localStorage.removeItem(NAMESPACE_KEY); - } else { - localStorage.setItem(NAMESPACE_KEY, namespaces); - } - const newSettings = this.debuggingNamespaces(); - if (this.logger) { - this.logger.options.consoleNamespaceWhitelist = newSettings; - } - return newSettings; - } catch (storageErr) { - console.log(localize("localStorage not available for debug namespace retrieval")); - } -}; /** string rep */ GalaxyApp.prototype.toString = function toString() { diff --git a/client/src/app/singleton.js b/client/src/app/singleton.js index 2fcb87470b7..fb9f147c428 100644 --- a/client/src/app/singleton.js +++ b/client/src/app/singleton.js @@ -2,7 +2,6 @@ * TODO: Make this part of galaxy.js later, once the global refs are gone */ -import addLogging from "utils/add-logging"; import config from "config"; import { GalaxyApp } from "./galaxy"; import { serverPath } from "utils/serverPath"; @@ -17,9 +16,6 @@ export function setGalaxyInstance(factory) { if (!(newInstance instanceof GalaxyApp)) { newInstance = new GalaxyApp(newInstance); } - if (newInstance.debug === undefined) { - addLogging(newInstance, "GalaxyApp"); - } storage._galaxyInstance = newInstance; diff --git a/client/src/app/user-model.js b/client/src/app/user-model.js index b382f24d841..a9859e4348c 100644 --- a/client/src/app/user-model.js +++ b/client/src/app/user-model.js @@ -3,14 +3,12 @@ import { getAppRoot } from "onload/loadConfig"; import baseMVC from "./base-mvc"; import _l from "utils/localization"; -var logNamespace = "user"; //============================================================================== /** @class Model for a Galaxy user (including anonymous users). * @name User */ -var User = Backbone.Model.extend(baseMVC.LoggableMixin).extend( +var User = Backbone.Model.extend( /** @lends User.prototype */ { - _logNamespace: logNamespace, /** API location for this resource */ urlRoot: function () { @@ -142,15 +140,6 @@ User.getCurrentUserFromApi = (options) => { return currentUser; }; -// (stub) collection for users (shouldn't be common unless admin UI) -//var UserCollection = Backbone.Collection.extend(baseMVC.LoggableMixin).extend({ -// model: User, -// urlRoot: function() { -// return `${getAppRoot()}api/users`; -// } -// //logger : console, -//}); - //============================================================================== export default { User: User, diff --git a/client/src/components/Form/Elements/parameters.js b/client/src/components/Form/Elements/parameters.js index c9fbc13a745..834c27e4d8e 100644 --- a/client/src/components/Form/Elements/parameters.js +++ b/client/src/components/Form/Elements/parameters.js @@ -42,7 +42,7 @@ export default Backbone.View.extend({ this.field = typeof this[fieldClass] === "function" ? this[fieldClass].call(this, input_def) : null; if (!this.field) { this.field = input_def.options ? this._fieldSelect(input_def) : this._fieldText(input_def); - Galaxy.emit.debug("form-parameters::_addRow()", `Auto matched field type (${input_def.type}).`); + console.debug("form-parameters::_addRow()", `Auto matched field type (${input_def.type}).`); } if (input_def.value === undefined) { input_def.value = null; diff --git a/client/src/components/Libraries/LibraryFolder/TopToolbar/library-model.js b/client/src/components/Libraries/LibraryFolder/TopToolbar/library-model.js index e81e344c49c..87b7c00d403 100644 --- a/client/src/components/Libraries/LibraryFolder/TopToolbar/library-model.js +++ b/client/src/components/Libraries/LibraryFolder/TopToolbar/library-model.js @@ -153,7 +153,7 @@ var FolderContainer = Backbone.Model.extend({ var file_item = new Ldda(obj.folder_contents[i]); this.get("folder").add(file_item); } else { - Galaxy.emit.error("Unknown folder item type encountered while parsing response."); + console.error("Unknown folder item type encountered while parsing response."); } } return obj; diff --git a/client/src/mvc/ui/ui-select-content.js b/client/src/mvc/ui/ui-select-content.js index 11de437c075..f99600dd44d 100644 --- a/client/src/mvc/ui/ui-select-content.js +++ b/client/src/mvc/ui/ui-select-content.js @@ -222,7 +222,7 @@ const View = Backbone.View.extend({ /** Return the currently selected dataset values */ value: function (new_value) { - const galaxy = getGalaxyInstance(); + const Galaxy = getGalaxyInstance(); if (new_value) { this._patchValue(new_value); this.model.set("value", new_value); @@ -240,7 +240,7 @@ const View = Backbone.View.extend({ const unpatchedValue = this._unpatchValue(details); result.values.push(unpatchedValue); } else { - galaxy.emit.debug( + console.debug( "ui-select-content::value()", `Requested details not found for '${id_list[i]}'.` ); @@ -252,7 +252,7 @@ const View = Backbone.View.extend({ } } } else { - galaxy.emit.debug("ui-select-content::value()", `Invalid value/source '${new_value}'.`); + console.debug("ui-select-content::value()", `Invalid value/source '${new_value}'.`); } return null; }, @@ -303,7 +303,7 @@ const View = Backbone.View.extend({ this.config = Configurations[config_id]; } else { this.config = Configurations["data"]; - galaxy.emit.debug("ui-select-content::_changeType()", `Invalid configuration/type id '${config_id}'.`); + console.debug("ui-select-content::_changeType()", `Invalid configuration/type id '${config_id}'.`); } // prepare extension component of error message diff --git a/client/src/mvc/visualization/chart/views/repeat.js b/client/src/mvc/visualization/chart/views/repeat.js index 49965337d01..1c48e22d01c 100644 --- a/client/src/mvc/visualization/chart/views/repeat.js +++ b/client/src/mvc/visualization/chart/views/repeat.js @@ -44,7 +44,7 @@ export var View = Backbone.View.extend({ add: function (options) { const Galaxy = getGalaxyInstance(); if (!options.id || this.list[options.id]) { - Galaxy.emit.debug("form-repeat::add()", "Duplicate or invalid repeat block id."); + console.debug("form-repeat::add()", "Duplicate or invalid repeat block id."); return; } var button_delete = new Ui.Button({ @@ -77,7 +77,7 @@ export var View = Backbone.View.extend({ del: function (id) { const Galaxy = getGalaxyInstance(); if (!this.list[id]) { - Galaxy.emit.debug("form-repeat::del()", "Invalid repeat block id."); + console.debug("form-repeat::del()", "Invalid repeat block id."); return; } this.$list.find(`#${id}`).remove(); diff --git a/client/src/utils/add-logging.js b/client/src/utils/add-logging.js deleted file mode 100644 index 9b9f040930d..00000000000 --- a/client/src/utils/add-logging.js +++ /dev/null @@ -1,35 +0,0 @@ -//============================================================================== -var LOGGING_FNS = ["log", "debug", "info", "warn", "error", "metric"]; -/** adds logging functions to an obj.prototype (or obj directly) adding a namespace for filtering - * @param {Object} obj - * @param {String} namespace - */ -function addLogging(obj, namespace) { - var addTo = obj.prototype !== undefined ? obj.prototype : obj; - if (namespace !== undefined) { - addTo._logNamespace = namespace; - } - - // give the object each - LOGGING_FNS.forEach((logFn) => { - addTo[logFn] = function () { - if (!this.logger) { - return undefined; - } - if (this.logger.emit) { - return this.logger.emit(logFn, this._logNamespace, arguments); - } - if (this.logger[logFn]) { - //TODO:! there has to be a way to get the lineno/file into this - // http://stackoverflow.com/questions/13815640/a-proper-wrapper-for-console-log-with-correct-line-number - // http://www.paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/ - return this.logger[logFn].apply(this.logger, arguments); - } - return undefined; - }; - }); - return obj; -} - -//============================================================================== -export default addLogging; diff --git a/client/src/utils/deferred.js b/client/src/utils/deferred.js index 7dfe38996a1..7828e5fab0b 100644 --- a/client/src/utils/deferred.js +++ b/client/src/utils/deferred.js @@ -29,7 +29,7 @@ export default Backbone.Model.extend({ process.promise().always(function () { delete self.active[id]; has_deferred && - Galaxy.emit.debug( + console.debug( "deferred::execute()", `${this.state().charAt(0).toUpperCase() + this.state().slice(1)} ${id}` ); @@ -38,7 +38,7 @@ export default Backbone.Model.extend({ // deferred queue $.when(this.last).always(() => { if (self.active[id]) { - has_deferred && Galaxy.emit.debug("deferred::execute()", `Running ${id}`); + has_deferred && console.debug("deferred::execute()", `Running ${id}`); callback(process); !has_deferred && process.resolve(); } else { @@ -52,7 +52,7 @@ export default Backbone.Model.extend({ */ reset: function () { const Galaxy = getGalaxyInstance(); - Galaxy.emit.debug("deferred::execute()", "Reset"); + console.debug("deferred::execute()", "Reset"); for (var i in this.active) { this.active[i] = false; } diff --git a/client/src/utils/metrics-logger.js b/client/src/utils/metrics-logger.js deleted file mode 100644 index ab9f5ea2338..00000000000 --- a/client/src/utils/metrics-logger.js +++ /dev/null @@ -1,504 +0,0 @@ -/*============================================================================= -TODO: - while anon: logs saved to 'logs-null' - this will never post - unless we manually do so at/after login - OR prepend when userId and localStorage has 'logs-null' - wire up _delayPost and test - -=============================================================================*/ -/** @class MetricsLogger - * - * Object to cache, output, and post log/metric messages to the server. - * Meant to be attached to the Galaxy object. - * - * Log from objects by either attaching logger directly: - * panel.logger.metric( 'user dataset deletion', panel.user.id, hda.toJSON() ) - * or using the LoggableMixin or addLogging function: - * MyBackboneModel.extend( LoggableMixin ).extend({ ... }) - * addLogging( MyBackboneModel, 'my-backbone-model' ) - * - * Log from templates by calling directly from Galaxy object: - * Galaxy.logger.metric( 'template loaded', { ownedByUser : true }); - * - * If you attempt to log an un-serializable object (circular reference, window, etc.), - * that entry will not be cached (or sent). If you set consoleLevel and consoleLogger - * appropriately, a warning will be shown when this happens: - * > panel.metric( 'something weird with window', { window : window }) - * !'Metrics logger could not stringify logArguments: ...' - */ -import jQuery from "jquery"; - -function MetricsLogger(options) { - options = options || {}; - var self = this; - - ///** get the current user's id from bootstrapped data or options */ - self.userId = window.bootstrapped && window.bootstrapped.user ? window.bootstrapped.user.id : null; - self.userId = self.userId || options.userId || null; - - /** the (optional) console to emit logs to */ - self.consoleLogger = options.consoleLogger || null; - - self._init(options); - return self; -} - -//----------------------------------------------------------------------------- defaults and constants -// see: python std lib, logging -MetricsLogger.ALL = 0; -MetricsLogger.LOG = 0; -MetricsLogger.DEBUG = 10; -MetricsLogger.INFO = 20; -MetricsLogger.WARN = 30; -MetricsLogger.ERROR = 40; -// metrics levels here? -//MetricsLogger.MinorEvent = 45; -//MetricsLogger.MajorEvent = 50; -MetricsLogger.METRIC = 50; -MetricsLogger.NONE = 100; - -/** default options - override these through the constructor */ -MetricsLogger.defaultOptions = { - /** if an incoming message has a level >= this, it will be cached - can also be a string (e.g. 'debug') */ - logLevel: MetricsLogger.NONE, - /** if an incoming message has a level >= this, it will be output to the console */ - consoleLevel: MetricsLogger.NONE, - /** the default 'namespace' or label associated with an incoming message (if none is passed) */ - defaultNamespace: "Galaxy", - /** the namespaces output to the console (all namespaces will be output if this is falsy) - * note: applies only to the console (not the event/metrics log/cache) - */ - consoleNamespaceWhitelist: null, - /** Force all messages into simple strings. */ - consoleFlattenMessages: false, - /** the prefix attached to client-side logs to distinguish them in the metrics db */ - clientPrefix: "client.", - - /** the maximum number of messages the cache should hold; if exceeded older messages are removed first */ - maxCacheSize: 3000, - /** the number of messages accumulate before posting to the server; should be <= maxCacheSize */ - postSize: 1000, - /** T/F whether to add a timestamp to incoming cached messages */ - addTime: true, - /** string to prefix to userid for cache web storage */ - cacheKeyPrefix: "logs-", - - /** the relative url to post messages to */ - postUrl: "/api/metrics", - /** delay before trying post again after two failures */ - delayPostInMs: 1000 * 60 * 10, - - /** an (optional) function that should return an object; used to send additional data with the metrics */ - getPingData: undefined, - /** an (optional) function that will handle the servers response after successfully posting messages */ - onServerResponse: undefined, -}; - -//----------------------------------------------------------------------------- set up -/** initialize the logger with options, set up instance vars and cache, and add onpageunload to window */ -MetricsLogger.prototype._init = function _init(options) { - var self = this; - self.options = {}; - for (var k in MetricsLogger.defaultOptions) { - if (Object.prototype.hasOwnProperty.call(MetricsLogger.defaultOptions, k)) { - self.options[k] = Object.prototype.hasOwnProperty.call(options, k) - ? options[k] - : MetricsLogger.defaultOptions[k]; - } - } - self.options.logLevel = self._parseLevel(self.options.logLevel); - self.options.consoleLevel = self._parseLevel(self.options.consoleLevel); - //self._emitToConsole( 'debug', 'MetricsLogger', 'MetricsLogger.options:', self.options ); - - /** is the logger currently sending? */ - self._sending = false; - /** the setTimeout id if the logger POST has failed more than once */ - self._waiting = null; - /** the current number of entries to send in a POST */ - self._postSize = self.options.postSize; - - self._initCache(); - - return self; -}; - -/** initialize the cache */ -MetricsLogger.prototype._initCache = function _initCache() { - try { - this.cache = new LoggingCache({ - maxSize: this.options.maxCacheSize, - key: this.options.cacheKeyPrefix + this.userId, - }); - } catch (err) { - this._emitToConsole("warn", "MetricsLogger", ["Could not intitialize logging cache:", err]); - this.options.logLevel = MetricsLogger.NONE; - } -}; - -/** return the numeric log level if level in 'none, debug, log, info, warn, error' */ -MetricsLogger.prototype._parseLevel = function _parseLevel(level) { - var type = typeof level; - if (type === "number") { - return level; - } - if (type === "string") { - var upper = level.toUpperCase(); - if (Object.prototype.hasOwnProperty.call(MetricsLogger, upper)) { - return MetricsLogger[upper]; - } - } - throw new Error(`Unknown log level: ${level}`); -}; - -//----------------------------------------------------------------------------- main entry point -/** record a log/message's arguments to the cache and/or the console based on level and namespace */ -MetricsLogger.prototype.emit = function emit(level, namespace, logArguments) { - //this._emitToConsole( 'debug', 'MetricsLogger', [ 'emit:', level, namespace, logArguments ]); - var self = this; - namespace = namespace || self.options.defaultNamespace; - if (!level || !logArguments) { - return self; - } - // add to cache if proper level - //TODO: respect do not track? - //if( !navigator.doNotTrack && level >= self.options.logLevel ){ - level = self._parseLevel(level); - if (level >= self.options.logLevel) { - self._addToCache(level, namespace, logArguments); - } - // also emit to consoleLogger if proper level for that - if (self.consoleLogger && level >= self.options.consoleLevel) { - self._emitToConsole(level, namespace, logArguments); - } - return self; -}; - -//----------------------------------------------------------------------------- cache -/** add a message to the cache and if messages.length is high enough post them to the server */ -MetricsLogger.prototype._addToCache = function _addToCache(level, namespace, logArguments) { - this._emitToConsole("debug", "MetricsLogger", [ - "_addToCache:", - arguments, - this.options.addTime, - this.cache.length(), - ]); - //this._emitToConsole( 'debug', 'MetricsLogger', [ '\t logArguments:', logArguments ]); - var self = this; - // try add to the cache and if we've got _postSize number of entries, attempt to post them to the server - try { - var newLength = self.cache.add(self._buildEntry(level, namespace, logArguments)); - if (newLength >= self._postSize) { - self._postCache(); - } - // discard entry if an error occurs, but warn if level set to do so - } catch (err) { - self._emitToConsole("warn", "MetricsLogger", [ - "Metrics logger could not stringify logArguments:", - namespace, - logArguments, - ]); - self._emitToConsole("error", "MetricsLogger", [err]); - } - return self; -}; - -/** build a log cache entry object from the given level, namespace, and arguments (optionally adding timestamp */ -MetricsLogger.prototype._buildEntry = function _buildEntry(level, namespace, logArguments) { - this._emitToConsole("debug", "MetricsLogger", ["_buildEntry:", arguments]); - var entry = { - level: level, - namespace: this.options.clientPrefix + namespace, - args: logArguments, - }; - if (this.options.addTime) { - entry.time = new Date().toISOString(); - } - return entry; -}; - -/** post _postSize messages from the cache to the server, removing them if successful - * if the post fails, wait until maxCacheSize is accumulated instead and try again then - * in addition to the messages from the cache ('metrics'), any info from getPingData (if set) will be sent - * onServerResponse will be called (if set) with any response from the server - */ -MetricsLogger.prototype._postCache = function _postCache(options) { - options = options || {}; - this._emitToConsole("info", "MetricsLogger", ["_postCache", options, this._postSize]); - - // short circuit if we're already sending - if (!this.options.postUrl || this._sending) { - return jQuery.when({}); - } - - var self = this; - var postSize = options.count || self._postSize; - - var // do not splice - remove after *successful* post - entries = self.cache.get(postSize); - - var entriesLength = entries.length; - - var // use the optional getPingData to add any extra info we may want to send - postData = typeof self.options.getPingData === "function" ? self.options.getPingData() : {}; - - //console.debug( postSize, entriesLength ); - - // add the metrics and send - postData.metrics = JSON.stringify(entries); - //console.debug( postData.metrics ); - self._sending = true; - return jQuery - .post(self.options.postUrl, postData) - .always(() => { - self._sending = false; - }) - .fail((xhr, status, message) => { - // if we failed the previous time, set the next post target to the max num of entries - self._postSize = self.options.maxCacheSize; - //TODO:?? - // log this failure to explain any gap in metrics - self.emit("error", "MetricsLogger", [ - "_postCache error:", - xhr.readyState, - xhr.status, - xhr.responseJSON || xhr.responseText, - ]); - //TODO: still doesn't solve the problem that when cache == max, post will be tried on every emit - //TODO: see _delayPost - }) - .done((response) => { - if (typeof self.options.onServerResponse === "function") { - self.options.onServerResponse(response); - } - // only remove if post successful - self.cache.remove(entriesLength); - //console.debug( 'removed entries:', entriesLength, 'size now:', self.cache.length() ); - // if we succeeded, reset the post target to the normal num of entries - self._postSize = self.options.postSize; - }); - // return the xhr promise -}; - -/** set _waiting to true and, after delayPostInMs, set it back to false */ -MetricsLogger.prototype._delayPost = function _delayPost() { - //TODO: this won't work between pages - var self = this; - self._waiting = window.setTimeout(() => { - self._waiting = null; - }, self.options.delayPostInMs); -}; - -function usefulToString(arg) { - var asStr = String(arg); - if (asStr == "[object Object]") { - try { - asStr = JSON.stringify(arg); - } catch (e) { - // If arg has cyclic reference, return String - // otherwise rendering stops and we have incomplete page - console.error(e); - return String(arg); - } - } - return asStr; -} - -//----------------------------------------------------------------------------- console -/** output message to console based on level and consoleLogger type */ -MetricsLogger.prototype._emitToConsole = function _emitToConsole(level, namespace, logArguments) { - //console.debug( '_emitToConsole:', level, namespace, logArguments ); - var self = this; - - var whitelist = self.options.consoleNamespaceWhitelist; - if (!self.consoleLogger) { - return self; - } - // if a whitelist for namespaces is set, bail if this namespace is not in the list - if (whitelist && whitelist.indexOf(namespace) === -1) { - return self; - } - - var args = Array.prototype.slice.call(logArguments, 0); - args.unshift(namespace); - if (self.options.consoleFlattenMessages) { - args = [args.map(usefulToString).join(" ")]; - } - //TODO: script location and/or source maps? - //TODO: branch on navigator.userAgent == AIIEEE - it only has log - if (level >= MetricsLogger.METRIC && typeof self.consoleLogger.info === "function") { - return self.consoleLogger.info.apply(self.consoleLogger, args); - } else if (level >= MetricsLogger.ERROR && typeof self.consoleLogger.error === "function") { - return self.consoleLogger.error.apply(self.consoleLogger, args); - } else if (level >= MetricsLogger.WARN && typeof self.consoleLogger.warn === "function") { - self.consoleLogger.warn.apply(self.consoleLogger, args); - } else if (level >= MetricsLogger.INFO && typeof self.consoleLogger.info === "function") { - self.consoleLogger.info.apply(self.consoleLogger, args); - } else if (level >= MetricsLogger.DEBUG && typeof self.consoleLogger.debug === "function") { - self.consoleLogger.debug.apply(self.consoleLogger, args); - } else if (typeof self.consoleLogger.log === "function") { - self.consoleLogger.log.apply(self.consoleLogger, args); - } - return self; -}; - -//----------------------------------------------------------------------------- shortcuts -// generic functions when logging from non-namespaced object (e.g. templates) -/** log to default namespace */ -MetricsLogger.prototype.log = function log() { - this.emit(1, this.options.defaultNamespace, Array.prototype.slice.call(arguments, 0)); -}; - -/** debug to default namespace */ -MetricsLogger.prototype.debug = function debug() { - this.emit(MetricsLogger.DEBUG, this.options.defaultNamespace, Array.prototype.slice.call(arguments, 0)); -}; - -/** info to default namespace */ -MetricsLogger.prototype.info = function info() { - this.emit(MetricsLogger.INFO, this.options.defaultNamespace, Array.prototype.slice.call(arguments, 0)); -}; - -/** warn to default namespace */ -MetricsLogger.prototype.warn = function warn() { - this.emit(MetricsLogger.WARN, this.options.defaultNamespace, Array.prototype.slice.call(arguments, 0)); -}; - -/** error to default namespace */ -MetricsLogger.prototype.error = function error() { - this.emit(MetricsLogger.ERROR, this.options.defaultNamespace, Array.prototype.slice.call(arguments, 0)); -}; - -/** metric to default namespace */ -MetricsLogger.prototype.metric = function metric() { - this.emit(MetricsLogger.METRIC, this.options.defaultNamespace, Array.prototype.slice.call(arguments, 0)); -}; - -/* ============================================================================ -TODO: - need a performance pass - the JSON un/parsing is a bit much - -============================================================================ */ -/** @class LoggingCache - * Simple implementation of cache wrapping an array. - * - * Formats an entry before it's cached and only keeps options.maxSize number - * of entries. Older entries are deleted first. - */ -function LoggingCache(options) { - var self = this; - return self._init(options || {}); -} - -/** default options */ -LoggingCache.defaultOptions = { - /** maximum number of entries to keep before discarding oldest */ - maxSize: 5000, -}; - -/** initialize with options */ -LoggingCache.prototype._init = function _init(options) { - if (!this._hasStorage()) { - //TODO: fall back to local storage - throw new Error("LoggingCache needs localStorage"); - } - if (!options.key) { - throw new Error("LoggingCache needs key for localStorage"); - } - this.key = options.key; - this._initStorage(); - - this.maxSize = options.maxSize || LoggingCache.defaultOptions.maxSize; - return this; -}; - -/** tests for localStorage fns */ -LoggingCache.prototype._hasStorage = function _hasStorage() { - //TODO: modernizr - var test = "test"; - try { - localStorage.setItem(test, test); - localStorage.removeItem(test); - return true; - } catch (e) { - return false; - } -}; - -/** if no localStorage set for key, initialize to empty array */ -LoggingCache.prototype._initStorage = function _initStorage() { - if (localStorage.getItem(this.key) === null) { - return this.empty(); - } - return this; -}; - -/** add an entry to the cache, removing the oldest beforehand if size >= maxSize */ -LoggingCache.prototype.add = function add(entry) { - var self = this; - var _cache = self._fetchAndParse(); - var overage = _cache.length + 1 - self.maxSize; - if (overage > 0) { - _cache.splice(0, overage); - } - _cache.push(entry); - self._unparseAndStore(_cache); - return _cache.length; -}; - -/** get the entries from localStorage and parse them */ -LoggingCache.prototype._fetchAndParse = function _fetchAndParse() { - var self = this; - return JSON.parse(localStorage.getItem(self.key)); -}; - -/** stringify the entries and put them in localStorage */ -LoggingCache.prototype._unparseAndStore = function _unparseAndStore(entries) { - var self = this; - return localStorage.setItem(self.key, JSON.stringify(entries)); -}; - -///** process the entry before caching */ -//LoggingCache.prototype._preprocessEntry = function _preprocessEntry( entry ){ -// return JSON.stringify( entry ); -//}; - -/** return the length --- oh, getters where are you? */ -LoggingCache.prototype.length = function length() { - return this._fetchAndParse().length; -}; - -/** get count number of entries starting with the oldest */ -LoggingCache.prototype.get = function get(count) { - return this._fetchAndParse().slice(0, count); -}; - -/** remove count number of entries starting with the oldest */ -LoggingCache.prototype.remove = function remove(count) { - var _cache = this._fetchAndParse(); - var removed = _cache.splice(0, count); - this._unparseAndStore(_cache); - return removed; -}; - -/** empty/clear the entire cache */ -LoggingCache.prototype.empty = function empty() { - localStorage.setItem(this.key, "[]"); - return this; -}; - -/** stringify count number of entries (but do not remove) */ -LoggingCache.prototype.stringify = function stringify(count) { - return JSON.stringify(this.get(count)); -}; - -/** outputs entire cache to console */ -LoggingCache.prototype.print = function print() { - // popup? (really, carl? a popup?) - easier to copy/paste - console.log(JSON.stringify(this._fetchAndParse(), null, " ")); -}; - -//============================================================================= -export default { - MetricsLogger: MetricsLogger, - LoggingCache: LoggingCache, -}; diff --git a/client/src/utils/metrics-logger.test.js b/client/src/utils/metrics-logger.test.js deleted file mode 100644 index 75608afc9a2..00000000000 --- a/client/src/utils/metrics-logger.test.js +++ /dev/null @@ -1,354 +0,0 @@ -// This file isn't really testing anything useful yet, it is just testing -// (or demonstrating) qunit+backbone interactions. - -import jQuery from "jquery"; -import sinon from "sinon"; -import metrics from "utils/metrics-logger"; - -const MockConsole = function () { - this.lastMessage = null; - ["log", "debug", "info", "warn", "error"].forEach((fnName) => { - this[fnName] = function () { - var args = Array.prototype.slice.call(arguments, 0); - this.lastMessage = { level: fnName, args: args }; - }; - }); - return this; -}; - -describe("metrics-logger tests", function () { - test("logger construction/initializiation defaults", function () { - const logger = new metrics.MetricsLogger({}); - expect(logger.consoleLogger).toEqual(null); - expect(logger.options.logLevel).toEqual(metrics.MetricsLogger.NONE); - expect(logger.options.consoleLevel).toEqual(metrics.MetricsLogger.NONE); - expect(logger.options.defaultNamespace).toEqual("Galaxy"); - expect(logger.options.clientPrefix).toEqual("client."); - expect(logger.options.postSize).toEqual(1000); - expect(logger.options.maxCacheSize).toEqual(3000); - expect(logger.options.addTime).toEqual(true); - expect(logger.options.postUrl).toEqual("/api/metrics"); - expect(logger.options.getPingData).toEqual(undefined); - expect(logger.options.onServerResponse).toEqual(undefined); - - expect(logger._postSize).toEqual(1000); - expect(logger.cache.constructor).toEqual(metrics.LoggingCache); - }); - - test("_parseLevel", function () { - const logger = new metrics.MetricsLogger({}); - expect(logger._parseLevel("all")).toEqual(metrics.MetricsLogger.ALL); - expect(logger._parseLevel("debug")).toEqual(metrics.MetricsLogger.DEBUG); - expect(logger._parseLevel("info")).toEqual(metrics.MetricsLogger.INFO); - expect(logger._parseLevel("warn")).toEqual(metrics.MetricsLogger.WARN); - expect(logger._parseLevel("error")).toEqual(metrics.MetricsLogger.ERROR); - expect(logger._parseLevel("metric")).toEqual(metrics.MetricsLogger.METRIC); - expect(logger._parseLevel("none")).toEqual(metrics.MetricsLogger.NONE); - expect(logger._parseLevel(15)).toEqual(15); - // - expect(() => { - logger._parseLevel(undefined); - }).toThrowError(/Unknown log level/); - expect(() => { - logger._parseLevel("nope"); - }).toThrowError(/Unknown log level/); - }); - - test("emit to cache at level", function () { - const logger = new metrics.MetricsLogger({ - logLevel: "metric", - }); - logger.cache.empty(); - - expect(logger.options.logLevel).toEqual(metrics.MetricsLogger.METRIC); - logger.emit("metric", "test", [1, 2, { three: 3 }]); - expect(logger.cache.length()).toEqual(1); - - const cached = logger.cache.get(1)[0]; - expect(cached.level).toEqual(metrics.MetricsLogger.METRIC); - expect(cached.namespace).toEqual("client.test"); - expect(cached.args.length).toEqual(3); - expect(cached.args[2].three).toEqual(3); - expect(typeof cached.time === "string").toBeTruthy(); - expect(cached.time === new Date(cached.time).toISOString()); - }); - - test("emit to cache below does not cache", function () { - const logger = new metrics.MetricsLogger({ - logLevel: "metric", - }); - logger.cache.empty(); - - logger.emit("error", "test", [1, 2, { three: 3 }]); - expect(logger.cache.length()).toBe(0); - }); - - test("emit to cache (silently) drops non-parsable", function () { - const logger = new metrics.MetricsLogger({ - logLevel: "metric", - }); - logger.cache.empty(); - - logger.emit("metric", "test", [{ window: window }]); - expect(logger.cache.length()).toBe(0); - }); - - // function metricsFromRequestBody(request) { - // // assumes 'metrics' is only entry in requestBody - // return JSON.parse(decodeURIComponent(request.requestBody.replace("metrics=", ""))); - // } - - // QUnit.test("_postCache success", function (assert) { - // var callback = sinon.spy(), - // logger = new metrics.MetricsLogger({ - // logLevel: "metric", - // onServerResponse: function (response) { - // callback(); - // }, - // }); - // logger.cache.empty(); - - // var server = sinon.fakeServer.create(), - // metricsOnServer; - // server.respondWith("POST", "/api/metrics", function (request) { - // metricsOnServer = metricsFromRequestBody(request); - // //console.debug( 'requestBody:', request.requestBody ); - // //console.debug( 'metricsOnServer:', JSON.stringify( metricsOnServer, null, ' ' ) ); - // request.respond( - // 200, - // { "Content-Type": "application/json" }, - // JSON.stringify({ - // fakeResponse: "yes", - // }) - // ); - // }); - - // logger.emit("metric", "test", [1, 2, { three: 3 }]); - // logger._postCache(); - // server.respond(); - - // assert.ok(callback.calledOnce, "onServerResponse was called"); - // assert.equal(logger.cache.length(), 0, "should have emptied cache (on success)"); - // assert.equal(logger._postSize, 1000, "_postSize still at default"); - - // // metrics were in proper form on server - // assert.equal(metricsOnServer.length, 1); - // var metric = metricsOnServer[0]; - // assert.equal(metric.level, metrics.MetricsLogger.METRIC); - // assert.equal(metric.namespace, "client.test"); - // assert.equal(metric.args.length, 3); - // assert.equal(metric.args[2].three, 3); - // assert.ok(typeof metric.time === "string"); - // assert.ok(metric.time === new Date(metric.time).toISOString()); - - // server.restore(); - // }); - - // QUnit.test("_postCache failure", function (assert) { - // var callback = sinon.spy(), - // logger = new metrics.MetricsLogger({ - // logLevel: "metric", - // onServerResponse: function (response) { - // callback(); - // }, - // }); - // logger.cache.empty(); - - // var server = sinon.fakeServer.create(); - // server.respondWith("POST", "/api/metrics", function (request) { - // request.respond( - // 500, - // { "Content-Type": "application/json" }, - // JSON.stringify({ - // err_msg: "NoooOPE!", - // }) - // ); - // }); - - // logger.emit("metric", "test", [1, 2, { three: 3 }]); - // logger._postCache(); - // server.respond(); - // //TODO: is the following what we want? - // assert.ok(!callback.calledOnce, "onServerResponse was NOT called"); - // assert.equal(logger.cache.length(), 1, "should NOT have emptied cache"); - // assert.equal(logger._postSize, logger.options.maxCacheSize, "_postSize changed to max"); - - // server.restore(); - // }); - - // // ------------------------------------------------------------------------ Emit to console - // QUnit.test("emit to console at level", function (assert) { - // var mockConsole = new MockConsole(), - // logger = new metrics.MetricsLogger({ - // consoleLevel: "debug", - // consoleLogger: mockConsole, - // }); - // assert.equal(logger.options.consoleLevel, metrics.MetricsLogger.DEBUG); - // assert.equal(logger.consoleLogger.constructor, MockConsole); - - // logger.emit("debug", "test", [1, 2, { three: 3 }]); - // assert.equal(logger.cache.length(), 1); - // //console.debug( JSON.stringify( mockConsole.lastMessage ) ); - // assert.equal(mockConsole.lastMessage.level, "debug"); - // assert.equal(mockConsole.lastMessage.args.length, 4); - // assert.equal(mockConsole.lastMessage.args[0], "test"); - // assert.equal(mockConsole.lastMessage.args[3].three, 3); - // }); - - // QUnit.test("emit to console below does not output", function (assert) { - // var mockConsole = new MockConsole(), - // logger = new metrics.MetricsLogger({ - // consoleLevel: "error", - // consoleLogger: mockConsole, - // }); - // logger.emit("debug", "test", [1, 2, { three: 3 }]); - // assert.equal(mockConsole.lastMessage, null); - // }); - - // // ------------------------------------------------------------------------ Shortcuts - // QUnit.test("logger shortcuts emit to default namespace properly", function (assert) { - // var logger = new metrics.MetricsLogger({ - // logLevel: "all", - // }); - // logger.cache.empty(); - - // assert.equal(logger.options.logLevel, metrics.MetricsLogger.ALL); - // logger.log(0); - // logger.debug(1); - // logger.info(2); - // logger.warn(3); - // logger.error(4); - // logger.metric(5); - - // assert.equal(logger.cache.length(), 6); - // var cached = logger.cache.remove(6), - // entry; - - // cached.forEach(function (entry) { - // assert.ok(entry.namespace === logger.options.clientPrefix + logger.options.defaultNamespace); - // assert.ok(jQuery.type(entry.args) === "array"); - // assert.ok(typeof entry.time === "string"); - // }); - - // // log is different - // entry = cached[0]; - // assert.ok(entry.level === 1); - // assert.ok(entry.args[0] === 0); - - // ["debug", "info", "warn", "error", "metric"].forEach(function (level, i) { - // entry = cached[i + 1]; - // assert.ok(entry.level === logger._parseLevel(level)); - // assert.ok(entry.args[0] === i + 1); - // }); - // }); - - // // ======================================================================== LoggingCache - // QUnit.test("cache construction/initializiation defaults", function (assert) { - // // use empty to prevent tests stepping on one another due to persistence - // var cache = new metrics.LoggingCache({ key: "logs-test" }).empty(); - // assert.equal(cache.maxSize, 5000); - // assert.equal(window.localStorage.getItem("logs-test"), "[]"); - // }); - - // QUnit.test("cache construction/initializiation failure", function (assert) { - // ////TODO: doesn't work - readonly - // //window.localStorage = null; - // //console.debug( 'localStorage:', window.localStorage ); - // var oldFn = metrics.LoggingCache.prototype._hasStorage; - // metrics.LoggingCache.prototype._hasStorage = function () { - // return false; - // }; - // assert.throws( - // function () { - // return new metrics.LoggingCache({ key: "logs-test" }); - // }, - // /LoggingCache needs localStorage/, - // "lack of localStorage throws error" - // ); - // metrics.LoggingCache.prototype._hasStorage = oldFn; - - // assert.throws( - // function () { - // return new metrics.LoggingCache(); - // }, - // /LoggingCache needs key for localStorage/, - // "lack of key throws error" - // ); - // }); - - // QUnit.test("cache construction/initializiation setting max cache size", function (assert) { - // var cache = new metrics.LoggingCache({ - // key: "logs-test", - // maxSize: 5, - // }).empty(); - // assert.equal(cache.maxSize, 5); - // }); - - // QUnit.test("cache plays well with no data", function (assert) { - // var cache = new metrics.LoggingCache({ key: "logs-test" }).empty(); - - // assert.equal(cache.length(), 0); - // var get = cache.get(10); - // assert.ok(jQuery.type(get) === "array" && get.length === 0); - // var remove = cache.remove(10); - // assert.ok(jQuery.type(remove) === "array" && remove.length === 0); - // assert.equal(cache.length(), 0); - // }); - - test("cache add properly adds and removes data", function () { - const cache = new metrics.LoggingCache({ - key: "logs-test", - maxSize: 5, - }).empty(); - - const entry1 = [{ one: 1 }, "two"]; - cache.add(entry1); - - expect(cache.length()).toBe(1); - expect(JSON.stringify(cache.get(1)[0])).toBe(JSON.stringify(entry1)); - - const entry2 = { blah: { one: 1 }, bler: ["three", { two: 2 }] }; - cache.add(entry2); - expect(cache.length()).toBe(2); - expect(cache.stringify(2)).toBe("[" + JSON.stringify(entry1) + "," + JSON.stringify(entry2) + "]"); - - // FIFO - var returned = cache.remove(1); - assert.equal(cache.length(), 1); - assert.ok(jQuery.type(returned) === "array" && returned.length === 1); - var returned0 = returned[0]; - assert.ok(jQuery.type(returned0) === "array" && JSON.stringify(returned0) === JSON.stringify(entry1)); - }); - - test("cache past max loses oldest", function () { - const cache = new metrics.LoggingCache({ - key: "logs-test", - maxSize: 5, - }).empty(); - - for (let i = 0; i < 10; i += 1) { - cache.add({ index: i }); - } - expect(cache.length()).toBe(5); - const get = cache.get(5); - expect(get[0].index === 5).toBeTruthy(); - expect(get[1].index === 6).toBeTruthy(); - expect(get[2].index === 7).toBeTruthy(); - expect(get[3].index === 8).toBeTruthy(); - expect(get[4].index === 9).toBeTruthy(); - }); - - test("cache is properly persistent", function () { - const cache1 = new metrics.LoggingCache({ key: "logs-test" }).empty(); - const entry = [{ one: 1 }, "two"]; - cache1.add(entry); - expect(cache1.length()).toBe(1); - - const cache2 = new metrics.LoggingCache({ key: "logs-test" }); - expect(cache2.length()).toBe(1); // , "old key gets previously stored"); - expect(JSON.stringify(cache2.get(1)[0])).toBe(JSON.stringify(entry)); - - const cache3 = new metrics.LoggingCache({ key: "logs-bler" }); - expect(cache3.length()).toBe(0); //, "new key causes new storage"); - }); -}); diff --git a/client/tests/qunit/test-app.js b/client/tests/qunit/test-app.js index bf7ebe36332..e5b7fc451f3 100644 --- a/client/tests/qunit/test-app.js +++ b/client/tests/qunit/test-app.js @@ -18,12 +18,6 @@ export function setupTestGalaxy(galaxyOptions_ = null) { galaxy.currHistoryPanel = { model: new Backbone.Model(), }; - galaxy.emit = { - debug: function () {}, - error: function (v) { - window.console.error(v); - }, - }; return galaxy; }); } diff --git a/client/tests/qunit/testBundle.js b/client/tests/qunit/testBundle.js index c10502e1c72..b6cf0381990 100644 --- a/client/tests/qunit/testBundle.js +++ b/client/tests/qunit/testBundle.js @@ -4,6 +4,5 @@ import "./tests/galaxy_app_base_tests"; import "./tests/jquery_plugin_tests"; -import "./tests/metrics_logger_tests"; import "./tests/modal_tests"; import "./tests/ui_tests"; diff --git a/client/tests/qunit/tests/galaxy_app_base_tests.js b/client/tests/qunit/tests/galaxy_app_base_tests.js index 0d18449f594..4e6150cf3e7 100644 --- a/client/tests/qunit/tests/galaxy_app_base_tests.js +++ b/client/tests/qunit/tests/galaxy_app_base_tests.js @@ -35,14 +35,6 @@ QUnit.test("App base extends from Backbone.Events", function (assert) { }); }); -QUnit.test("App base has logging methods from utils/add-logging.js", function (assert) { - var app = getGalaxyInstance(); - ["debug", "info", "warn", "error", "metric"].forEach(function (fn) { - assert.ok(typeof app[fn] === "function"); - }); - assert.ok(app._logNamespace === "GalaxyApp"); -}); - // We no longer want this behavior QUnit.test("App base will patch in attributes from existing Galaxy objects", function (assert) { var existingApp = getGalaxyInstance();