From b0bd3e95d3f99e578974a644b038af33bca20315 Mon Sep 17 00:00:00 2001 From: Jarek Rencz Date: Mon, 26 Sep 2016 17:07:28 +0200 Subject: [PATCH] Allow configuring logger (Fix #19) --- segment.js | 116 +++++++++++++++++++++++++++++++++++- segment.min.js | 2 +- src/config.js | 9 +++ src/provider.js | 107 ++++++++++++++++++++++++++++++++- test/segmentSpec.js | 142 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 368 insertions(+), 8 deletions(-) diff --git a/segment.js b/segment.js index 4519667..84447f8 100644 --- a/segment.js +++ b/segment.js @@ -24,6 +24,15 @@ angular.module('ngSegment').constant('segmentDefaultConfig', { // Debug: turns debug statements on/off. Useful during development. debug: false, + // Allowed values: + // - string (in which case it will be interpreted as a name of a + // service debug messages will be emitted with, E.g. `$log`) + // - object + logger: '$log', + + // Decides which method of the logger to use if debug is turned on. + debugLevel: 'log', + // Methods: the analytics.js methods that the service creates queueing stubs for. methods: [ 'trackSubmit', @@ -187,7 +196,7 @@ angular.module('ngSegment').constant('segmentDefaultConfig', { debug: function () { if (this.config.debug) { arguments[0] = this.config.tag + arguments[0]; - console.log.apply(console, arguments); + this.logger[this.config.debugLevel].apply(this.logger, arguments); return true; } }, @@ -237,6 +246,18 @@ angular.module('ngSegment').constant('segmentDefaultConfig', { return this; }; + this.setLogger = function (logger) { + this.config.logger = logger; + this.validate('logger'); + return this; + }; + + this.setDebugLevel = function (level) { + this.config.debugLevel = level; + this.validate('debugLevel'); + return this; + }; + this.setEvents = function (events) { this.events = events; return this; @@ -289,6 +310,58 @@ angular.module('ngSegment').constant('segmentDefaultConfig', { throw new Error(config.tag + 'Condition callback must be a function or array.'); } }, + + logger: function (config) { + if ( + !angular.isFunction(config.logger) && + !angular.isObject(config.logger) && + !angular.isString(config.logger) && + (config.logger != null) + ) { + throw new Error(config.tag + 'Logger must be either' + + ' an object or a string or a function or' + + ' undefined/null.'); + } + + var debugLevelType = typeof config.debugLevel; + + if (angular.isObject(config.logger)) { + if (debugLevelType !== 'string') { + throw new Error(config.tag + 'Logger given' + + ' as an object requires specifying' + + ' `debugLevel` config as a string.' + + ' `debugLevel` was a `' + debugLevelType + '`'); + } + if (!(config.debugLevel in config.logger)) { + throw new Error(config.tag + 'Logger given' + + ' as an object does not have a method called `' + + config.debugLevel + '` (set as' + + ' `debugLevel`). Methods in the given' + + ' object are: `' + Object + .keys(config.logger) + .filter(function (propName) { + return angular.isFunction(config.logger[propName]) + }) + .join(', ') + '`'); + } + } + + if (angular.isFunction(config.logger)) { + if (debugLevelType !== 'string') { + throw new Error(config.tag + 'Logger given' + + ' as a function requires specifying' + + ' `debugLevel` config as a string.' + + ' `debugLevel` was a `' + debugLevelType + '`'); + } + } + }, + + debugLevel: function (config) { + if (!angular.isString(config.debugLevel)) { + throw new Error(config.tag + '`debugLevel` must be' + + ' a string.'); + } + } }; // Allows validating a specific property after set[Prop] @@ -300,6 +373,12 @@ angular.module('ngSegment').constant('segmentDefaultConfig', { }; this.createService = function ($injector, segmentLoader) { + var provider = this; + + // Logger will be configured as soon as possible but `segmentConfig` + // may set up the logger because of that we need to gather messages + // logged before logger is set up. + var preLoggerDebugMessages = []; // Apply user-provided config constant if it exists if ($injector.has('segmentConfig')) { @@ -309,7 +388,7 @@ angular.module('ngSegment').constant('segmentDefaultConfig', { } angular.extend(this.config, constant); - this.debug('Found segment config constant'); + preLoggerDebugMessages.push('Found segment config constant'); // Validate settings passed in by constant var _this = this; @@ -318,6 +397,33 @@ angular.module('ngSegment').constant('segmentDefaultConfig', { }); } + if ('logger' in this.config && this.config.logger) { + switch (typeof this.config.logger) { + case 'string': + if (!$injector.has(this.config.logger)) { + throw new Error(this.config.tag + 'Logger' + + ' service `' + this.config.logger + '` is not' + + ' known.'); + } + this.logger = $injector.get(this.config.logger); + break; + case 'object': + this.logger = this.config.logger; + break; + case 'function': + this.logger = {}; + this.logger[this.config.debugLevel] = + this.config.logger.bind(undefined); + break; + } + } else { + this.logger = $injector.get('$log'); + } + + preLoggerDebugMessages.forEach(function (message) { + provider.debug(message); + }); + // Autoload Segment on service instantiation if an API key has been set via the provider if (this.config.autoload) { this.debug('Autoloading Analytics.js'); @@ -356,6 +462,12 @@ angular.module('ngSegment').constant('segmentDefaultConfig', { segment[item.method].apply(segment, item.arguments); }); + // public debug has to be shadowed because it need to have + // access to the provider. + segment.debug = function () { + return Segment.prototype.debug.apply(provider, arguments); + }; + return segment; }; diff --git a/segment.min.js b/segment.min.js index b60f522..7991ea6 100644 --- a/segment.min.js +++ b/segment.min.js @@ -1 +1 @@ -angular.module("ngSegment",[]),angular.module("ngSegment").constant("segmentDefaultConfig",{apiKey:null,autoload:!0,loadDelay:0,condition:null,debug:!1,methods:["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","page","once","off","on"],tag:"[ngSegment] "}),function(a){function b(a){this.hasLoaded=a||!1,this.load=function(a,b){if(window.analytics.initialized&&console.warn("Warning: Segment analytics has already been initialized. Did you already load the library?"),this.hasLoaded)throw new Error("Attempting to load Segment twice.");if(!a)throw new Error("Cannot load Analytics.js without an API key.");this.hasLoaded=!0,window.setTimeout(function(){var b=document.createElement("script");b.type="text/javascript",b.async=!0,b.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.com/analytics.js/v1/"+a+"/analytics.min.js",b.onerror=function(){console.error("Error loading Segment library.")};var c=document.getElementsByTagName("script")[0];c.parentNode.insertBefore(b,c)},b)}}function c(){b.call(this),this.$get=function(){return new b(this.hasLoaded)}}a.provider("segmentLoader",c)}(angular.module("ngSegment")),function(a){function b(a){this.config=a,this.factory=function(a){var b=this;return function(){return b.config.condition&&!b.config.condition(a,arguments)?void b.debug("Not calling method, condition returned false.",{method:a,arguments:arguments}):(b.debug("Calling method "+a+" with arguments:",arguments),window.analytics[a].apply(d,arguments))}}}function c(a){this.config=angular.copy(a),this.queue=[],this.factory=function(a){var b=this.queue;return function(){b.push({method:a,arguments:arguments})}},this.init(),this.setKey=function(a){return this.config.apiKey=a,this.validate("apiKey"),this},this.setLoadDelay=function(a){return this.config.loadDelay=a,this.validate("loadDelay"),this},this.setCondition=function(a){return this.config.condition=a,this.validate("condition"),this},this.setEvents=function(a){return this.events=a,this},this.setConfig=function(a){if(!angular.isObject(a))throw new Error(this.config.tag+"Config must be an object.");angular.extend(this.config,a);var b=this;return Object.keys(a).forEach(function(a){b.validate(a)}),this},this.setAutoload=function(a){return this.config.autoload=!!a,this},this.setDebug=function(a){return this.config.debug=!!a,this};var c={apiKey:function(a){if(!angular.isString(a.apiKey)||!a.apiKey)throw new Error(a.tag+"API key must be a valid string.")},loadDelay:function(a){if(!angular.isNumber(a.loadDelay))throw new Error(a.tag+"Load delay must be a number.")},condition:function(a){if(!(angular.isFunction(a.condition)||angular.isArray(a.condition)&&angular.isFunction(a.condition[a.condition.length-1])))throw new Error(a.tag+"Condition callback must be a function or array.")}};this.validate=function(a){"function"==typeof c[a]&&c[a](this.config)},this.createService=function(a,c){if(a.has("segmentConfig")){var d=a.get("segmentConfig");if(!angular.isObject(d))throw new Error(this.config.tag+"Config constant must be an object.");angular.extend(this.config,d),this.debug("Found segment config constant");var e=this;Object.keys(d).forEach(function(a){e.validate(a)})}if(this.config.autoload&&(this.debug("Autoloading Analytics.js"),this.config.apiKey?c.load(this.config.apiKey,this.config.loadDelay):this.debug(this.config.tag+" Warning: API key is not set and autoload is not disabled.")),"function"==typeof this.config.condition||"array"==typeof this.config.condition&&"function"==typeof this.config.condition[this.config.condition-1]){var f=this.config.condition;this.config.condition=function(b,c){return a.invoke(f,f,{method:b,params:c})}}var g=new b(angular.copy(this.config));return this.events&&(g.events=angular.copy(this.events)),g.init(),this.queue.forEach(function(a){g[a.method].apply(g,a.arguments)}),g},this.$get=["$injector","segmentLoader",this.createService]}var d=window.analytics=window.analytics||[];d.invoked?console.error("Segment or ngSegment included twice."):d.invoked=!0,d.factory=function(a){return function(){var b=Array.prototype.slice.call(arguments);return b.unshift(a),d.push(b),d}},b.prototype={init:function(){for(var a=0;a