From cd476138fb6498d993b5de7495a2ee6bf0e394cc Mon Sep 17 00:00:00 2001 From: Cory Silva Date: Fri, 18 Sep 2015 06:56:34 -0700 Subject: [PATCH] Added hostName and documentation. --- client/bugLogClientAngular.js | 278 +++++++++++++++++++--------------- 1 file changed, 155 insertions(+), 123 deletions(-) diff --git a/client/bugLogClientAngular.js b/client/bugLogClientAngular.js index 69e4486..30de9dc 100644 --- a/client/bugLogClientAngular.js +++ b/client/bugLogClientAngular.js @@ -1,134 +1,166 @@ -angular.module('BugLogHq', []) -//the application to tell BugLog from where the report is coming from. -.constant('appName', "app") -.constant('bugLogConfig', { - // this is the location of the BugLog server that will be receiving the error reports (needs to be a REST/POST endpoint) - listener: "", - // the hostname to tell BugLog from where the report is coming from. Leave empty to get the info from the browser. - hostName: "", - // If the BugLog server requires an API Key to talk to it, then here is where you put it. - apiKey: "" -}) -.config(['$provide', - function ($provide) { - $provide.decorator('$exceptionHandler', ['$delegate', '$log', 'BugLogService', - function ($delegate, $log, BugLogService) { - return function(exception, cause) { - //Custom error handling. - try { - BugLogService.notifyService({ - message: cause, - error: exception - }); - } - catch (e) { - $log.warn("BugLog Service: Error logging bug"); - $log.warn(e); - } - // Calls the original $exceptionHandler. - $delegate(exception, cause); - }; - } - ]); - } -]) -.factory('BugLogService', ["$window", "$document", "appName", "bugLogConfig", - function BugLogService($window, $document, appName, bugLogConfig) { - var BugLog = { - listener: bugLogConfig.listener, - hostName: bugLogConfig.hostName, - appName: appName, - apiKey: bugLogConfig.apiKey, - - notifyService: function(message) { - var msg = {}; // set defaults - if(typeof message == "string") - msg.message = message; - msg.message = message.message; - msg.extraInfo = message.extraInfo || ""; - msg.severity = message.severity || "ERROR"; - msg.error = message.error || undefined; - msg.stack = message.stack || undefined; +/** + * This module requires stacktrace-js and angular, it has been tested with: + * + * "stacktrace-js": "~0.6.4" + * "angular": "~1.3.15" + */ - // get additional information (if any) - // get assets here for file version reference - var extra = { - assets: BugLog.getAssets(), - errorUrl: $window.location.href - }; - if(msg.extraInfo) { - extra += "

Extra Info:
"; - if(typeof msg.extraInfo=="string") { - extra += msg.extraInfo; - } else { - if(window.JSON) { - extra += JSON.stringify(msg.extraInfo); +angular.module('BugLogHq', []) + .constant('bugLogConfig', { + // This is the location of the BugLog server that will be receiving the error reports + // example: 'https://domain/buglog/listeners/bugLogListenerREST.cfm' + listener: '', + // Tell BugLog which application is submitting this bug + applicationCode: '', + // If the BugLog server requires an API Key to talk to it, then here is where you put it. + apiKey: '', + // The hostname to tell BugLog from where the report is coming from. Leave empty to get the info from the browser. + hostName: '', + // Default bug serverity code + defaultSeverity: 'ERROR' + }) + .config(['$provide', + function ($provide) { + $provide.decorator('$exceptionHandler', ['$delegate', '$log', 'BugLogService', + function ($delegate, $log, BugLogService) { + return function(exception, cause) { + // Custom error handling. + try { + BugLogService.logBug(cause, exception); } - } - } - - // see if we can get a stacktrace - var stacktrace = undefined; - if(typeof msg.stack !== "undefined") { - stacktrace = msg.stack; - } else if(typeof msg.error !== "undefined") { - stacktrace = (typeof msg.error.stack!=="undefined") ? msg.error.stack : $window.printStackTrace({e:msg.error}).join("\n"); - } - if(typeof stacktrace !== "undefined") { - extra += "

Stacktrace:
"+stacktrace+"
"; + catch (e) { + $log.warn("BugLog Service: Error logging bug"); + $log.warn(e); + } + // Calls the original $exceptionHandler. + $delegate(exception, cause); + }; } - - - // build the message in a format that can be passed along - var data = "message="+escape(msg.message) - +"&severityCode="+escape(msg.severity) - +"&hostName="+escape(BugLog.hostName || $window.location.host) - +"&applicationCode="+escape(BugLog.appName) - +"&apiKey="+escape(BugLog.apiKey) - +"&userAgent="+escape(navigator.userAgent) - +"&templatePath="+escape($document.URL) - +"&htmlReport=" + escape(extra); - - // create the notifier - var noti = BugLog.createNotifier("script"); - noti.src = BugLog.listener + "?" + data; + ]); + } + ]) + .factory('BugLogService', function BugLogService($window, bugLogConfig) { + var service = { + logBug: logBug + }; + return service; + ///////////////////// - // destroy the notifier - BugLog.destroyNotifier(noti); - }, + /** + * Communicates with a BugLogHq Server + * @param {string} cause + * @param {object} error + * @param {string} [severityCode] + */ + function logBug(cause, error, severityCode) { + var data = {}; + data.message = 'Caused by: ' + cause; + data.exceptionMessage = cause || 'Unknown'; + data.exceptionDetails = buildStacktrace(error) || 'something went wrong'; + data.severityCode = severityCode || bugLogConfig.defaultSeverity || 'ERROR'; + data.HTMLReport = buildHtmlReport() || 'something went wrong'; + data.userAgent = navigator.userAgent || 'Unknown'; + data.templatePath = document.URL || 'Unknown'; + data.applicationCode = bugLogConfig.applicationCode || 'Unknown'; + data.APIKey = bugLogConfig.apiKey || ''; + data.hostName = bugLogConfig.hostName || ''; + // Make server call; we could use the $http via the $injector service (to avoid circular dependencies) + // but customer header could be set that upset CORS requests to the bugLog server + // so a plain old javascript XHR request is used. + var xhr = new XMLHttpRequest(); + xhr.open('POST', encodeURI(bugLogConfig.listener)); + xhr.setRequestHeader('Content-Type', 'application/json'); + xhr.send(angular.toJson(data)); + } - createNotifier: function(tagName) { - var notifier = document.createElement(tagName); - notifier.id = "buglog" + (+ new Date); - var head = document.getElementsByTagName("head")[0]; - head.appendChild(notifier); - return notifier; - }, + /** + * Build Stacktrace + * @param error + * @return {*} + */ + function buildStacktrace(error) { + var stack; + if(typeof error.stack !== 'undefined') { + stack = error.stack; + } + if ($window.printStackTrace) { + stack = $window.printStackTrace({e: error}).join('\n'); + } + return stack; + } - destroyNotifier: function(notifier) { - var elm = document.getElementById(notifier.id); - var head = document.getElementsByTagName("head")[0]; - head.removeChild(elm); - }, + /** + * Build HTML Report + * The $rootScope is included because it holds our user session object + * If your app uses a service instead the $injector service can be used to retrieve it + * @returns {string} + */ + function buildHtmlReport() { + var html = '

Bug Report

'; + var rootScope = getRootScope() || 'something went wrong'; + var scripts = getScripts() || 'something went wrong'; + var stylesheets = getStylesheets() || 'something went wrong'; + html += '

RootScope:

' + angular.toJson(rootScope, 2) + '
'; + html += '

Scripts:

' + angular.toJson(scripts, 2) + '
'; + html += '

Stylesheets:

' + angular.toJson(stylesheets, 2) + '
'; + return html; + } - getAssets: function () { - var assets = []; - var links = document.getElementsByTagName("link"); - for(var i = 0; i < links.length; i++) { - if (links[i].href) { - assets.push(links[i].href); - } + /** + * Gets an Html Document's stylesheets + * @returns {Array} + */ + function getStylesheets() { + var assets = []; + var links = document.getElementsByTagName('link'); + for(var i = 0; i < links.length; i++) { + if (links[i].href && links[i].rel && links[i].rel.toLowerCase() === 'stylesheet') { + var href = links[i].href.split('/'); + assets.push(href[href.length-1]); } - var scripts = document.getElementsByTagName("script"); - for(var j = 0; j < scripts.length; j++) { - if (scripts[j].src) { - assets.push(scripts[j].src); - } + } + return assets; + } + + /** + * Gets an Html Document's scripts + * @returns {Array} + */ + function getScripts() { + var assets = []; + var scripts = document.getElementsByTagName('script'); + for(var j = 0; j < scripts.length; j++) { + if (scripts[j].src) { + var src = scripts[j].src.split('/'); + assets.push(src[src.length-1]); } - return assets; } - }; + return assets; + } + + /** + * Finds an AngularJs rootScope and returns its properties + * @returns {{}} + */ + function getRootScope() { + var body = angular.element(document.body); + var rootScope = body.scope().$root || undefined; + return cleanScope(rootScope); + } - return BugLog; - } -]); + /** + * Returns an object with copied properties from an AngularJs scope + * @param scope + * @returns {{}} + */ + function cleanScope(scope) { + var obj = {}; + for (var key in scope) { + if(key.substring(0, 1) !== '$' && key.substring(0,2) !== '__' && key !== 'constructor') { + obj[key] = angular.copy(scope[key]); + } + } + return obj; + } + }) +;