-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Add support for transport plugins #183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c043afe
e0bb8ef
c33201a
e0126b4
e893c65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,57 @@ Why are plugins needed at all? | |
|
||
JavaScript is pretty restrictive when it comes to exception handling, and there are a lot of things that make it difficult to get relevent information, so it's important that we inject code and wrap things magically so we can extract what we need. See :doc:`/usage/index` for tips regarding that. | ||
|
||
Transport plugins | ||
~~~~~~~~~~~~~~~~~ | ||
Transport plugins allow you to override how Raven sends the data into Sentry. | ||
A transport is added based on the dsn passed into config, e.g `Raven.config('http://[email protected]:80/2')` will use the transport registered for `http`. | ||
|
||
The default transport plugin uses inserts an image thus generating a `GET` request to Sentry. | ||
There is also a transport plugin available for `CORS XHR` in situations where the `Origin` and `Referer` headers won't get set properly for `GET` requests (e.g when the browser is executing from a `file://` URI). | ||
|
||
Registering new transport plugins | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
New methods of transports can be added to Raven using | ||
|
||
.. code-block:: javascript | ||
|
||
Raven.registerTransport('https+post', { | ||
/* | ||
* Configure the transport protocol using the provided config | ||
*/ | ||
setup: function(dsn, triggerEvent){ | ||
// snip | ||
}, | ||
|
||
/* | ||
* Send the data to Sentry. | ||
*/ | ||
send: function(data, endpoint){ | ||
// snip | ||
} | ||
}); | ||
|
||
|
||
A transport's `setup` method receives a `triggerEvent` parameter which | ||
can be used to trigger an event when the request had succeeded or failed, | ||
using: | ||
|
||
.. code-block:: javascript | ||
|
||
img.onload = function success() { | ||
triggerEvent('success', { | ||
data: data, | ||
src: src | ||
}); | ||
}; | ||
img.onerror = img.onabort = function failure() { | ||
triggerEvent('failure', { | ||
data: data, | ||
src: src | ||
}); | ||
}; | ||
|
||
|
||
|
||
All Plugins | ||
~~~~~~~~~~~ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/** | ||
* Chrome/V8 XHR Post plugin | ||
* | ||
* Allows raven-js to send log entries using XHR and CORS for | ||
* environments where the Origin and Referer headers are missing (e.g Chrome plugins). | ||
* | ||
* Usage: Raven.config('https+post://...'); | ||
* */ | ||
|
||
;(function(window, Raven, undefined){ | ||
'use strict'; | ||
var V8Transport = window.V8Transport = { | ||
setup: function(dsn, triggerEvent){ | ||
if(!this.hasCORS() && window.console && console.error){ | ||
console.error('This browser lacks support for CORS. Falling back to the default transport'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. |
||
delete dsn.pass; | ||
HTTPGetTransport.setup(dsn); | ||
return HTTPGetTransport; | ||
} | ||
|
||
if(!dsn.pass) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd recommend also checking if the user wants https here so they could also use http+post if(!dsn.pass && dsn.protocol == 'https+post') |
||
throw new RavenConfigError('The https+post V8 transport needs the private key to be set in the DSN.'); | ||
|
||
this.triggerEvent = triggerEvent; | ||
this.dsn = dsn; | ||
return this; | ||
}, | ||
|
||
hasCORS: function(){ | ||
return 'withCredentials' in new XMLHttpRequest(); | ||
}, | ||
|
||
getAuthString: function(){ | ||
if (this.cachedAuth) return this.cachedAuth; | ||
|
||
var qs = [ | ||
'sentry_version=4', | ||
'sentry_client=raven-js/' + Raven.VERSION, | ||
'sentry_key=' + this.dsn.user, | ||
'sentry_secret=' + this.dsn.pass | ||
]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd make pass optional for people who just want http. var qs = [
'sentry_version=4',
'sentry_client=raven-js/' + Raven.VERSION,
'sentry_key=' + this.dsn.user
];
if (this.dsn.pass) {
qs.push('sentry_secret=' + this.dsn.pass);
} |
||
|
||
return this.cachedAuth = 'Sentry ' + qs.join(','); | ||
}, | ||
|
||
send: function(data, endpoint){ | ||
var xhr = new XMLHttpRequest(), | ||
triggerEvent = this.triggerEvent; | ||
|
||
|
||
xhr.open('POST', endpoint, true); | ||
xhr.setRequestHeader('X-Sentry-Auth', this.getAuthString()); | ||
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8'); | ||
|
||
xhr.onload = function success() { | ||
triggerEvent('success', { | ||
data: data, | ||
src: endpoint | ||
}); | ||
}; | ||
xhr.onerror = xhr.onabort = function failure() { | ||
triggerEvent('failure', { | ||
data: data, | ||
src: endpoint | ||
}); | ||
}; | ||
|
||
xhr.send(JSON.stringify(data)); | ||
} | ||
} | ||
|
||
Raven.registerTransport('https+post', V8Transport); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And I would add Raven.registerTransport('http+post', V8Transport); |
||
}(this, Raven)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,49 @@ | ||
'use strict'; | ||
|
||
/* | ||
* Default transport. | ||
* */ | ||
var HTTPGetTransport = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .. and this one ImageTransport? |
||
setup: function(dsn, triggerEvent){ | ||
if (dsn.pass) | ||
throw new RavenConfigError('Do not specify your private key in the DSN!'); | ||
this.dsn = dsn; | ||
this.triggerEvent = triggerEvent; | ||
}, | ||
send: function(data, endpoint){ | ||
var img = new Image(), | ||
triggerEvent = this.triggerEvent, | ||
src = endpoint + this.getAuthQueryString() + '&sentry_data=' + encodeURIComponent(JSON.stringify(data)); | ||
|
||
img.onload = function success() { | ||
triggerEvent('success', { | ||
data: data, | ||
src: src | ||
}); | ||
}; | ||
img.onerror = img.onabort = function failure() { | ||
triggerEvent('failure', { | ||
data: data, | ||
src: src | ||
}); | ||
}; | ||
img.src = src; | ||
}, | ||
|
||
getAuthQueryString: function() { | ||
if (this.cachedAuth) return this.cachedAuth; | ||
var qs = [ | ||
'sentry_version=4', | ||
'sentry_client=raven-js/' + Raven.VERSION | ||
]; | ||
if (globalKey) { | ||
qs.push('sentry_key=' + this.dsn.user); | ||
} | ||
|
||
return this.cachedAuth = '?' + qs.join('&'); | ||
} | ||
}; | ||
|
||
// First, check for JSON support | ||
// If there is no JSON, we no-op the core features of Raven | ||
// since JSON is required to encode the payload | ||
|
@@ -10,6 +54,8 @@ var _Raven = window.Raven, | |
globalUser, | ||
globalKey, | ||
globalProject, | ||
globalTransports = { 'default': HTTPGetTransport }, | ||
globalTransport = null, | ||
globalOptions = { | ||
logger: 'javascript', | ||
ignoreErrors: [], | ||
|
@@ -21,6 +67,7 @@ var _Raven = window.Raven, | |
extra: {} | ||
}; | ||
|
||
|
||
/* | ||
* The core Raven singleton | ||
* | ||
|
@@ -95,7 +142,9 @@ var Raven = { | |
'/' + path + 'api/' + globalProject + '/store/'; | ||
|
||
if (uri.protocol) { | ||
globalServer = uri.protocol + ':' + globalServer; | ||
// Only use the first part of the url if it contains | ||
// a plugin identifier, e.g https+post -> https | ||
globalServer = uri.protocol.split('+')[0] + ':' + globalServer; | ||
} | ||
|
||
if (globalOptions.fetchContext) { | ||
|
@@ -108,6 +157,13 @@ var Raven = { | |
|
||
TraceKit.collectWindowErrors = !!globalOptions.collectWindowErrors; | ||
|
||
globalTransport = uri.protocol && globalTransports[uri.protocol] ? globalTransports[uri.protocol] : globalTransports['default']; | ||
|
||
// Allow transports to fall back to other transport protocols, | ||
// if the chosen one isn't supported | ||
var transport = globalTransport.setup(uri, triggerEvent); | ||
globalTransport = transport ? transport : globalTransport; | ||
|
||
// return for chaining | ||
return Raven; | ||
}, | ||
|
@@ -128,6 +184,23 @@ var Raven = { | |
return Raven; | ||
}, | ||
|
||
|
||
|
||
/* | ||
* Register a transport plugin for pushing data into Sentry. | ||
* Transport plugins are chosen based upon the DSN passed into Raven.configure, | ||
* e.g Raven.configure('http://...') will use the plugin registered for 'http'. | ||
* | ||
* @param {string} protocol A protocol identifier in the DSN, e.g http or https+post | ||
* @param {object} transport An implementation of the transport. Must implement the `setup(dsn)` and `send(data, endpoint)` methods. | ||
*/ | ||
registerTransport: function(protocol, transport){ | ||
if(globalTransports[protocol]) throw new RavenConfigError('Protocol ' + protocol + ' already has a registered transport method'); | ||
|
||
globalTransports[protocol] = transport; | ||
return Raven; | ||
}, | ||
|
||
/* | ||
* Wrap code within a context so Raven can capture errors | ||
* reliably across domains that is executed immediately. | ||
|
@@ -321,7 +394,7 @@ function triggerEvent(eventType, options) { | |
} | ||
|
||
var dsnKeys = 'source protocol user pass host port path'.split(' '), | ||
dsnPattern = /^(?:(\w+):)?\/\/(\w+)(:\w+)?@([\w\.-]+)(?::(\d+))?(\/.*)/; | ||
dsnPattern = /^(?:(\w+|\w+\+\w+)?:)?\/\/(\w+)(?::(\w+))?@([\w\.-]+)(?::(\d+))?(\/.*)/; | ||
|
||
function RavenConfigError(message) { | ||
this.name = 'RavenConfigError'; | ||
|
@@ -334,17 +407,14 @@ RavenConfigError.prototype.constructor = RavenConfigError; | |
function parseDSN(str) { | ||
var m = dsnPattern.exec(str), | ||
dsn = {}, | ||
i = 7; | ||
i = dsnKeys.length; | ||
|
||
try { | ||
while (i--) dsn[dsnKeys[i]] = m[i] || ''; | ||
} catch(e) { | ||
throw new RavenConfigError('Invalid DSN: ' + str); | ||
} | ||
|
||
if (dsn.pass) | ||
throw new RavenConfigError('Do not specify your private key in the DSN!'); | ||
|
||
return dsn; | ||
} | ||
|
||
|
@@ -395,23 +465,6 @@ function each(obj, callback) { | |
} | ||
} | ||
|
||
var cachedAuth; | ||
|
||
function getAuthQueryString() { | ||
if (cachedAuth) return cachedAuth; | ||
|
||
var qs = [ | ||
'sentry_version=4', | ||
'sentry_client=raven-js/' + Raven.VERSION | ||
]; | ||
if (globalKey) { | ||
qs.push('sentry_key=' + globalKey); | ||
} | ||
|
||
cachedAuth = '?' + qs.join('&'); | ||
return cachedAuth; | ||
} | ||
|
||
function handleStackInfo(stackInfo, options) { | ||
var frames = []; | ||
|
||
|
@@ -619,22 +672,7 @@ function send(data) { | |
|
||
|
||
function makeRequest(data) { | ||
var img = new Image(), | ||
src = globalServer + getAuthQueryString() + '&sentry_data=' + encodeURIComponent(JSON.stringify(data)); | ||
|
||
img.onload = function success() { | ||
triggerEvent('success', { | ||
data: data, | ||
src: src | ||
}); | ||
}; | ||
img.onerror = img.onabort = function failure() { | ||
triggerEvent('failure', { | ||
data: data, | ||
src: src | ||
}); | ||
}; | ||
img.src = src; | ||
globalTransport.send(data, globalServer); | ||
} | ||
|
||
function isSetup() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not call this one XHRTransport?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because sadly, this doesn't just work everywhere that XHR is accepted, so this is specific to V8.