Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Bugsense.js implementation

  • Loading branch information...
commit a663e9a8f963687838f4626225229fcf46d653a8 0 parents
Yves Van Goethem yvg authored

Showing 4 changed files with 196 additions and 0 deletions. Show diff stats Hide diff stats

  1. +9 0 LICENSE.txt
  2. +45 0 README.md
  3. +9 0 bugsense.css
  4. +133 0 bugsense.js
9 LICENSE.txt
... ... @@ -0,0 +1,9 @@
  1 +Copyright (c) 2011, SoundCloud Ltd., Yves Van Goethem
  2 +All rights reserved.
  3 +
  4 +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
  5 +
  6 + Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  7 + Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  8 +
  9 +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45 README.md
Source Rendered
... ... @@ -0,0 +1,45 @@
  1 +# JavaScript BugSense Notifier
  2 +
  3 +A front-end implementation of the BugSense API to track errors.
  4 +You can find the license in the LICENSE file at the root of this project.
  5 +
  6 +## Running
  7 +
  8 +We don't recommend you to implement this code as-is because it is designed for our specific needs and relies on top of different frameworks that we use across our site: jQuery, underscore.js, JSON.js, Backbone.js,…
  9 +But it is easily adaptable to any other context as described bellow.
  10 +
  11 +## API Structure
  12 +
  13 +The BugSense API is designed in 4 different parts:
  14 +
  15 +- application_environment (describes the environment the script runs in).
  16 +- client (defines the client name, protocol,…)
  17 +- exception (logs the "Class", message, backtrace,…)
  18 +- request (logs the URL and any other custom data (like xhr responses or requests))
  19 +
  20 +You will easily recognize these different parts in our source besides required parameters like api key, bugsense url, etc…
  21 +
  22 +## Calling the notifier
  23 +
  24 +If you use jQuery you are good to go:
  25 +
  26 + $(document).ajaxError(function(event, xhr, settings, error) {
  27 + SC.Bugsense.notify({
  28 + request: xhr,
  29 + settings: settings,
  30 + error: error
  31 + });
  32 + });
  33 +
  34 +As we use it we stick to the jQuery XHR structure to pass the different objects.
  35 +
  36 +## Tackle limitations
  37 +
  38 +### Class
  39 +
  40 +There are no proper Classes in JS, but this doesn't mean you can't have an equivalent structure.
  41 +As we are using Backbone, we extended their model and name them, this permits us to integrate it in the `exception.klass` object of BugSense.
  42 +
  43 +### TraceStack
  44 +
  45 +This is a little more tricky, looking into console.trace might be a good alternative to our code.
9 bugsense.css
... ... @@ -0,0 +1,9 @@
  1 +#bugsense-iframe {
  2 + max-width: 1px !important;
  3 + max-height: 1px !important;
  4 + top: 0;
  5 + left: 0;
  6 + position: absolute;
  7 + z-index: -1;
  8 + visibility: hidden;
  9 +}
133 bugsense.js
... ... @@ -0,0 +1,133 @@
  1 +SC.Bugsense = {
  2 + notify: function(notice) {
  3 +
  4 + var that = this;
  5 + this.notice = notice;
  6 + this.status = ( this.notice && this.notice.request && this.notice.request.status ).toString();
  7 + this.errors = ( this.notice && this.notice.request && this.notice.request.errors );
  8 + var res = this.notice.request || { message: 'Unknown Error' };
  9 + var stack = ( this.notice.response && this.notice.response.stack );
  10 +
  11 + // filter out some errors (e.g. 404, 422,…)
  12 + if (this.errorFilters()) {
  13 + return;
  14 + }
  15 +
  16 + // bugsense parameters (required)
  17 + this.defaults = {
  18 + apiKey: 'FOOBAR', // modify me
  19 + url: 'https://bugsense.appspot.com/api/errors?api_key=' // SSL
  20 + // url: 'http://www.bugsense.com/api/js/errors?api_key=' // NON-SSL
  21 + };
  22 +
  23 + this.data = {
  24 +
  25 + // basic data (required)
  26 + application_environment: {
  27 + environment: 'development', // modify me if you like
  28 + // TODO: find a way to detect the mobile device, maybe with WURFL or so…?
  29 + appver: window.navigator.userAgent || 'unknown',
  30 + osver: window.navigator.oscpu || 'unknown'
  31 + },
  32 +
  33 + // bugsense client
  34 + client: {
  35 + name : 'SC Mobile Bugsense Notifier',
  36 + protocol_version: 1,
  37 + version: '0.1'
  38 + },
  39 +
  40 + // basics about the exception
  41 + exception: {
  42 + klass: ( that.notice.settings && that.notice.settings.modelType ) || 'Unknown Component',
  43 + message: that.notice.error || res.message,
  44 + backtrace: that.generateBackTrace(stack),
  45 + where:"n/a:0" // can't take this out or the API breaks.
  46 + },
  47 +
  48 + // details & custom data about the exception including url, request, response,…
  49 + request: (function() {
  50 + var request = {
  51 + // Collecting IPs is illegal in some countries that's why we don't do it, if you'd like to, just remove this ligne
  52 + remote_ip: '0.0.0.0',
  53 + url: window.location.href,
  54 + custom_data: {
  55 + // You can remove & add custom data here from session/localStorage, cookies, geolocation, language, mimetypes,…
  56 + document_referrer : that.escapeText(document.referrer),
  57 + http_status : that.escapeText(this.status),
  58 + navigator_user_agent : that.escapeText(navigator.userAgent),
  59 + navigator_platform : that.escapeText(navigator.platform),
  60 + navigator_vendor : that.escapeText(navigator.vendor),
  61 + navigator_language : that.escapeText(navigator.language),
  62 + screen_width : that.escapeText(screen.width),
  63 + screen_height : that.escapeText(screen.height),
  64 + response : that.escapeText(that.notice.request.responseText),
  65 + request : {}
  66 + }
  67 + };
  68 + if (that.notice.settings) {
  69 + var req = that.notice.settings;
  70 + _.each(req, function(value, key) {
  71 + // whitelist to avoid functions
  72 + if (/boolean|number|string/.test($.type(value))) {
  73 + request.custom_data.request[key] = value;
  74 + }
  75 + });
  76 + }
  77 + // stringify it
  78 + request.custom_data.request = JSON.stringify(request.custom_data.request);
  79 + return request;
  80 + }())
  81 +
  82 + };
  83 +
  84 + // all ready? lets make a get request with the data
  85 + if (this.data && this.defaults.url && this.defaults.apiKey) {
  86 + var url = this.defaults.url + this.defaults.apiKey + '&data=' + escape( JSON.stringify(this.data) );
  87 + if ($('#bugsense-iframe')[0]) {
  88 + $('#bugsense-iframe').attr('src', url);
  89 + } else {
  90 + $('body').append('<iframe id="bugsense-iframe" src="' + url + '" width="1" height="1">');
  91 + }
  92 + }
  93 + },
  94 + errorFilters: function() {
  95 + return _.any([
  96 + // not found
  97 + this.status === '404',
  98 + // add whatever you want in here
  99 + ]);
  100 + },
  101 + escapeText: function(text) {
  102 + text = text.toString() || '';
  103 + return text.replace(/&/g, '&#38;')
  104 + .replace(/</g, '&#60;')
  105 + .replace(/>/g, '&#62;')
  106 + .replace(/'/g, '&#39;')
  107 + .replace(/"/g, '&#34;');
  108 + },
  109 + generateBackTrace: function(stack) {
  110 + if (stack) {
  111 + return stack.file + ':' + stack.line;
  112 + }
  113 + try {
  114 + throw new Error();
  115 + } catch (e) {
  116 + if (e.stack) {
  117 + var matcher = /\s+at\s(.+)\s\((.+?):(\d+)(:\d+)?\)/;
  118 + return $.map(e.stack.split("\n").slice(4), _.bind(function(line) {
  119 + var match = line.match(matcher);
  120 + var method = escapeText(match[1]);
  121 + var file = escapeText(match[2]);
  122 + var number = match[3];
  123 + return file + ':' + number + 'in' + method;
  124 + }, this)).join("\n");
  125 + } else if (e.sourceURL) {
  126 + // note: this is completely useless, as it just points back at itself but is needed on Safari
  127 + // keeping it around in case they ever end up providing actual stacktraces
  128 + return e.sourceURL + ':' + e.line;
  129 + }
  130 + }
  131 + return 'n/a:0';
  132 + }
  133 +};

0 comments on commit a663e9a

Please sign in to comment.
Something went wrong with that request. Please try again.