Skip to content

Instantly share code, notes, and snippets.

@amitaibu
Forked from mrkelly/event.builder.js
Last active August 29, 2015 14:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amitaibu/1b18bd8707f5bc9b59a3 to your computer and use it in GitHub Desktop.
Save amitaibu/1b18bd8707f5bc9b59a3 to your computer and use it in GitHub Desktop.
angular.module('hf.events.builder', [
'app.resources',
'app.values'
])
.factory('EventBuilder', function ($injector, $window, apiUrl) {
var EventBuilder = function EventBuilder(message) {
this['@timestamp'] = new Date();
this['@message'] = message || '';
return this;
};
EventBuilder.prototype.ensureFields = function() {
if (!this['@fields']) {
this['@fields'] = {};
}
};
EventBuilder.prototype.ensureError = function() {
this.ensureFields();
if (!this['@fields'].error) {
this['@fields'].error = {};
}
};
EventBuilder.prototype.ensureState = function() {
this.ensureFields();
if (!this['@fields'].state) {
this['@fields'].state = {};
}
};
EventBuilder.prototype.ensureStateError = function() {
this.ensureState();
if (!this['@fields'].state.error) {
this['@fields'].state.error = {};
}
};
EventBuilder.prototype.ensureClient = function() {
this.ensureFields();
if (!this['@fields'].client) {
this['@fields'].client = {};
}
};
EventBuilder.prototype.ensurePerformance = function() {
this.ensureClient();
if (!this['@fields'].client.performance) {
this['@fields'].client.performance = {};
}
};
EventBuilder.prototype.ensureScreen = function() {
this.ensureClient();
if (!this['@fields'].client.screen) {
this['@fields'].client.screen = {};
}
};
EventBuilder.prototype.message = function(message) {
this['@message'] = message;
return this;
};
EventBuilder.prototype.level = function(level) {
this.ensureFields();
this['@fields'].level = level;
return this;
};
EventBuilder.prototype.clientSource = function(clientSource) {
this.ensureFields();
this['@fields'].clientSource = clientSource;
return this;
};
EventBuilder.prototype.addUser = function() {
var securityContext = $injector.get('securityContext');
this.ensureFields();
// Set the user information to the current user or anonyous
if (securityContext.authenticated) {
this['@fields'].user = securityContext.user ? _.pick(securityContext.user, ['_id', 'name', 'email']) : undefined;
} else {
this['@fields'].user = {
_id: undefined,
name: 'anonymous',
email: undefined
};
}
return this;
};
EventBuilder.prototype.addException = function(exception, cause) {
if (exception) {
this.ensureError();
this['@fields'].error.message = exception.message ? exception.message : undefined;
this['@fields'].error.stack = exception.stack ? exception.stack : undefined;
this['@fields'].error.trace = exception.stack ? exception.stack.split('\n') : [];
this['@fields'].error.cause = cause ? cause : undefined;
}
return this;
};
EventBuilder.prototype.addState = function(toState, toParams, fromState, fromParams) {
var picks = [
'abstract',
'url',
'controller',
'template',
'templateUrl'
];
if (toState || toParams || fromState || fromParams) {
this.ensureState();
this['@fields'].state.toState = toState && _.keys(toState).length > 0 ? _.pick(toState, picks) : undefined;
this['@fields'].state.toParams = toParams && _.keys(toParams).length > 0 ? toParams : undefined;
this['@fields'].state.fromState = fromState && _.keys(fromState).length > 0 ? _.pick(fromState, picks) : undefined;
this['@fields'].state.fromParams = fromParams && _.keys(fromParams).length > 0 ? fromParams : undefined;
} else {
this.addCurrentState();
}
return this;
};
EventBuilder.prototype.addCurrentState = function() {
var $state = $injector.get('$state');
if ($state && $state.current) {
this.ensureState();
this['@fields'].state.current = $state.current ? $state.current : undefined;
this['@fields'].state.params = $state.params ? $state.params : undefined;
}
return this;
};
EventBuilder.prototype.addStateResolutionError = function(error) {
this.ensureState();
if (error.status) {
this.ensureStateError();
this['@fields'].state.error.status = error.status;
}
if (error.statusText) {
this.ensureStateError();
this['@fields'].state.error.statusText = error.statusText;
}
if (error.config) {
this.ensureStateError();
this['@fields'].state.error.config = _.pick(error.config, ['method', 'url']);
}
return this;
};
EventBuilder.prototype.addClient = function() {
this.addUserAgent();
this.addScreen();
this.addPerformance();
return this;
};
EventBuilder.prototype.addUserAgent = function() {
if ($window.navigator && $window.navigator.userAgent) {
this.ensureClient();
this['@fields'].client.userAgent = $window.navigator.userAgent;
}
return this;
};
EventBuilder.prototype.addScreen = function() {
if ($window.screen) {
this.ensureScreen();
if ($window.screen.height) {
this['@fields'].client.screen.height = $window.screen.height;
}
if ($window.screen.width) {
this['@fields'].client.screen.width = $window.screen.width;
}
if ($window.screen.colorDepth) {
this['@fields'].client.screen.colorDepth = $window.screen.colorDepth;
}
}
return this;
};
EventBuilder.prototype.addPerformance = function() {
if ($window.performance) {
this.ensurePerformance();
if ($window.performance.memory) {
this['@fields'].client.performance.memory = $window.performance.memory;
}
}
return this;
};
EventBuilder.prototype.shipIt = function() {
// We need to use jQuery here so that it'll be outside of angular scope
// It's possible that we could trigger infinite digests by modifying scope
// that tiggeres a watch statement that then triggers this which triggers another
// digest and so on.
$.ajax({
type: 'POST',
url: apiUrl + '/client/logs',
data: JSON.stringify(_.cloneDeep(this)),
contentType: 'application/json',
});
};
return EventBuilder;
});
angular.module('hf.events', [
'hf.events.handlers'
])
.run(function(api) {
api.add({
resource: 'logs',
url: '/client/logs'
});
});
angular.module('hf.events.handlers', [
'hf.events.builder'
])
.run(function($rootScope, EventBuilder) {
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error){
new EventBuilder()
.level('error')
.clientSource('$stateChangeError')
.message('State failed to resolve')
.addUser()
.addClient()
.addState(toState, toParams, fromState, fromParams)
.addStateResolutionError(error)
.shipIt();
});
})
.factory('$exceptionHandler', function ($log, EventBuilder) {
return function (exception, cause) {
// Keep the default error handling implementation
$log.error.apply($log, arguments);
try {
new EventBuilder()
.level('error')
.clientSource('$exceptionHandler')
.message(exception.message ? exception.message : null)
.addUser()
.addClient()
.addState()
.addException(exception, cause)
.shipIt();
} catch(err) {
$log.error(err);
}
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment