mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-24 12:13:33 +08:00
Drop metrics-logger
This commit is contained in:
@@ -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).
|
||||
* <br />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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user