diff --git a/.travis.yml b/.travis.yml index 1d948f8..5b06f6d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: node_js node_js: + - "6.1.0" - "4.1" - "0.12" - "0.11" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..08f0ddd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,68 @@ +## 0.9.0 + +- Add capability to send custom data with Express handler +- Treat custom errors as Errors +- `useSSL` option now works correctly, and support added for HTTP proxies +- If network errors occur during payload posting, and a Node-style error callback param is available on the callback, this is now executed +- Functional sending tests now pass correctly + +## 0.8.5 + +- Add ability to turn off 'humanised-object-strings' + +## 0.8.4 +- Add some smarts around passing an object in to the exception parameter + +## 0.8.3 +- Turn strings into errors if passed through. Log out request errors. + +## 0.8.2 +- Add setTags method + +## 0.8.1 +- Add custom error grouping key + +## 0.8.0 +- Add offline support + +## 0.7.1 +- Default useSSL to true + +## 0.7.0 +- Add onBeforeSend hook, api endpoint options, and bug fixes + +## 0.6.2 +- Fix utf8 chars causing 400s, log when errors occur when posting + +## 0.6.1 +- Replace deprecated request.host with request.hostname if it exists + +## 0.6.0 +- Added ability to send tags with exception reports + +## 0.5.0 +- Added filters for sensitive request data, and better affected user tracking + +## 0.4.2 +- Minor test refactor + +## 0.4.1 +- Fixed issue where getting cpu information returned undefined + +## 0.4.0 +- Added *user* function, deprecated setUser + +## 0.3.0 +- Added version and user tracking functionality; bump jshint version, update test + +## 0.2.0 +- Added Express handler, bug fixes + +## 0.1.2 +- Include more error information + +## 0.1.1 +- Point at the correct API endpoint, include correct dependencies for NPM + +## 0.1.0 +- Initial release \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT index 02abb2a..cddede7 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2015 Martin Holman +Copyright (c) 2016 Raygun Limited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/README.md b/README.md index 5faa577..f7fe81a 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,17 @@ app.use(raygunClient.expressHandler); The [Express documentation](http://expressjs.com/guide/error-handling.html) says `Though not strictly required, by convention you define error-handling middleware last, after other app.use() calls`, but that is incorrect. If the `app.use(raygunClient.expressHandler);` call is not immediately before the `app.listen` call, then errors will not be handled by Raygun. +Note that the Express middleware handler will pick up and transmit any `err` objects that reach it. If the app code itself chooses to handle states that result in 4xx/5xx status codes, these will not result in an error payload sent to Raygun. + ## Documentation +### Callbacks + +The callback should be a node-style callback: `function(err, response) { /*...*/ }`. +*Note*: If the callback only takes one parameter (`function(response){ /*...*/ }`) +it will only be called when the transmission is successful. This is included for +backwards compatibility; the Node-style callback should be preferred. + ### Sending custom data You can pass custom data in on the Send() function, as the second parameter. For instance (based off the call in test/raygun_test.js): @@ -30,6 +39,18 @@ You can pass custom data in on the Send() function, as the second parameter. For client.send(new Error(), { 'mykey': 'beta' }, function (response){ }); ``` +#### Sending custom data with Expressjs + +If you're using the `raygunClient.expressHandler`, you can send custom data along by setting `raygunClient.expressCustomData` to a function. The function will get two parameters, the error being thrown, and the request object. + +```javascript +var raygunClient = new raygun.Client().init({apiKey: "yourkey"}); + +raygunClient.expressCustomData = function (err, req) { + return { 'level': err.level }; +}; +``` + ### Callback ```javascript @@ -76,7 +97,13 @@ var raygunClient = new raygun.Client().init({apiKey: "yourkey"}); raygunClient.user = function (req) { if (req.user) { - return req.user.username; + return { + identifier: req.user.username, + email: req.user.email, + fullName: req.user.fullName, + firstName: req.user.firstName, + uuid: req.user.deviceID + }; } } ``` @@ -217,44 +244,30 @@ When initializing Raygun, pass through a `groupingKey` function. ### Custom error objects -By default Raygun4Node tries to convert unknown objects into a human readable string to help with grouping, this doens't always make sense. +By default Raygun4Node tries to convert unknown objects into a human readable string to help with grouping, this doesn't always make sense. -To disable it +To disable it: var raygunClient = new raygun.Client().init({ apiKey: 'YOUR_KEY', useHumanStringForObject: false }); +If your custom error object inherits from `Error` as its parent prototype, this isn't necessary however and these will be sent correctly. + ### Examples View a screencast on creating an app with Node.js and Express.js, then hooking up the error handling and sending them at [http://raygun.io/blog/2013/07/video-nodejs-error-handling-with-raygun/](http://raygun.io/blog/2013/07/video-nodejs-error-handling-with-raygun/) ## Contributing In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using "npm test". +To get the functional sending tests passing, set a `RAYGUN_APIKEY` environment variable to a valid apikey, e.g `export RAYGUN_APIKEY=your_apikey`. + ## Release History -- 0.8.5 - Add ability to turn off 'humanised-object-strings' -- 0.8.4 - Add some smarts around passing an object in to the exception parameter -- 0.8.3 - Turn strings into errors if passed through. Log out request errors. -- 0.8.2 - Add setTags method -- 0.8.1 - Add custom error grouping key -- 0.8.0 - Add offline support -- 0.7.1 - Default useSSL to true -- 0.7.0 - Add onBeforeSend hook, api endpoint options, and bug fixes -- 0.6.2 - Fix utf8 chars causing 400s, log when errors occur when posting -- 0.6.1 - Replace deprecated request.host with request.hostname if it exists -- 0.6.0 - Added ability to send tags with exception reports -- 0.5.0 - Added filters for sensitive request data, and better affected user tracking -- 0.4.2 - Minor test refactor -- 0.4.1 - Fixed issue where getting cpu information returned undefined -- 0.4.0 - Added *user* function, deprecated setUser -- 0.3.0 - Added version and user tracking functionality; bump jshint version, update test -- 0.2.0 - Added Express handler, bug fixes -- 0.1.2 - Include more error information -- 0.1.1 - Point at the correct API endpoint, include correct dependencies for NPM -- 0.1.0 - Initial release +[View the changelog here](CHANGELOG.md) ## License -Copyright (c) 2015 MindscapeHQ -Licensed under the MIT license. +Copyright (c) 2016 Raygun Limited + +Licensed under the MIT license. \ No newline at end of file diff --git a/lib/raygun.js b/lib/raygun.js index 5c151b9..b209861 100644 --- a/lib/raygun.js +++ b/lib/raygun.js @@ -22,7 +22,7 @@ var Raygun = function () { _filters = options.filters; _host = options.host; _port = options.port; - _useSSL = options.useSSL || true; + _useSSL = options.useSSL !== false; _onBeforeSend = options.onBeforeSend; _offlineStorage = options.offlineStorage || new OfflineStorage(); _offlineStorageOptions = options.offlineStorageOptions; @@ -49,6 +49,10 @@ var Raygun = function () { return raygun; }; + raygun.expressCustomData = function () { + return {}; + }; + raygun.setVersion = function (version) { _version = version; return raygun; @@ -128,7 +132,15 @@ var Raygun = function () { }; raygun.expressHandler = function (err, req, res, next) { - raygun.send(err, {}, function () { + var customData; + + if (typeof raygun.expressCustomData === 'function') { + customData = raygun.expressCustomData(err, req); + } else { + customData = raygun.expressCustomData; + } + + raygun.send(err, customData || {}, function () { }, req); next(err); }; diff --git a/lib/raygun.messageBuilder.js b/lib/raygun.messageBuilder.js index 6e30615..147524d 100644 --- a/lib/raygun.messageBuilder.js +++ b/lib/raygun.messageBuilder.js @@ -50,9 +50,7 @@ var RaygunMessageBuilder = function (options) { }; this.setErrorDetails = function (error) { - var errorType = Object.prototype.toString.call(error); - - if (errorType === '[object Object]' && options.useHumanStringForObject) { + if (!(error instanceof Error) && options.useHumanStringForObject) { error = humanString(error); message.details.groupingKey = error.replace(/\W+/g, "").substring(0, 64); } diff --git a/lib/raygun.transport.js b/lib/raygun.transport.js index 956f5bf..16fd7a9 100644 --- a/lib/raygun.transport.js +++ b/lib/raygun.transport.js @@ -11,23 +11,40 @@ var http = require('http'); var https = require('https'); +var API_HOST = 'api.raygun.io'; + +var getFullPath = function(options) { + var useSSL = options.useSSL, + port = useSSL ? 443 : 80, + protocol = useSSL ? 'https' : 'http'; + + return protocol + '://' + API_HOST + ':' + port + '/entries'; +}; + var send = function (options) { try { var data = new Buffer(JSON.stringify(options.message), 'utf8'); + var fullPath = getFullPath(options); + var httpOptions = { - host: options.host || 'api.raygun.io', + host: options.host || API_HOST, port: options.port || 443, - path: '/entries', + path: fullPath, method: 'POST', headers: { - 'Content-Type': 'application/json', - 'Content-Length': data.length, - 'X-ApiKey': options.apiKey + 'Host': API_HOST, + 'Content-Type': 'application/json', + 'Content-Length': data.length, + 'X-ApiKey': options.apiKey } }; var cb = function (response) { if (options.callback) { - options.callback(response); + if (options.callback.length > 1) { + options.callback(null, response); + } else { + options.callback(response); + } } }; var httpLib = options.useSSL ? https : http; @@ -35,8 +52,13 @@ var send = function (options) { request.on("error", function (e) { console.log("Raygun: error " + e.message + " occurred while attempting to send error with message: " + options.message); + + // If the callback has two parameters, it should expect an `error` value. + if (options.callback && options.callback.length > 1) { + options.callback(e); + } }); - + request.write(data); request.end(); } catch (e) { diff --git a/package.json b/package.json index b22b489..b3b7c92 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "raygun", "description": "Raygun.io plugin for Node", - "version": "0.8.5", + "version": "0.9.0", "homepage": "https://github.com/MindscapeHQ/raygun4node", "author": { "name": "MindscapeHQ", @@ -30,7 +30,7 @@ "devDependencies": { "jshint": "^2.5.6", "semver": "^5.0.3", - "tap": "^0.4.13" + "tap": "^7.1.2" }, "dependencies": { "object-to-human-string": "0.0.3", diff --git a/test/raygun.messageBuilder_test.js b/test/raygun.messageBuilder_test.js index 1a9a286..75e512f 100644 --- a/test/raygun.messageBuilder_test.js +++ b/test/raygun.messageBuilder_test.js @@ -65,9 +65,8 @@ test('basic builder tests', function (t) { tt.notOk(message.details.groupingKey); tt.equal('NoMessage', message.details.error.message); tt.end(); + t.end(); }); - - t.end(); }); test('error builder tests', function (t) { @@ -82,10 +81,10 @@ test('error builder tests', function (t) { t.test('stack trace', function (tt) { tt.ok(message.details.error.stackTrace); - var lines = 8; + var lines = 14; - if(semver.satisfies(process.version, '>=4.0.0')) { - lines = 10; + if (semver.satisfies(process.version, '>=6.0.0')) { + lines = 15; } tt.equal(message.details.error.stackTrace.length, lines); @@ -133,6 +132,7 @@ test('error builder tests', function (t) { tt.ok(message.details.error.message); tt.equals(message.details.error.message, errorMessage); tt.end(); + t.end(); }); }); @@ -172,9 +172,8 @@ test('custom data builder', function (t) { var message = builder.build(); tt.equals(message.details.userCustomData, undefined); tt.end(); + t.end(); }); - - t.end(); }); test('express4 request builder', function (t) { @@ -238,9 +237,8 @@ test('user and version builder tests', function (t) { var message = builder.build(); tt.equals(message.details.version, '1.0.0.0'); tt.end(); + t.end(); }); - - t.end(); }); test('filter keys tests', function (t) { @@ -269,6 +267,7 @@ test('filter keys tests', function (t) { tt.equals(message.details.request.headers['X-ApiKey'], undefined); tt.equals(message.details.request.headers['Host'], 'app.raygun.io'); tt.end(); + t.end(); }); }); @@ -307,5 +306,6 @@ test('custom tags', function (t) { tt.notOk(message.details.tags); tt.end(); + t.end(); }); }); diff --git a/test/raygun_send_test.js b/test/raygun_send_test.js index c3e58b1..66aafd3 100644 --- a/test/raygun_send_test.js +++ b/test/raygun_send_test.js @@ -1,33 +1,56 @@ 'use strict'; var test = require("tap").test; +var semver = require('semver'); var Raygun = require('../lib/raygun.js'); -// need to get these working, they time out for some reason, despite the call succeeding - -test('send basic', {skip: true}, function (t) { +test('send basic', {}, function (t) { t.plan(1); + + if (semver.satisfies(process.version, '=0.10')) { + t.pass('Ignored on node 0.10'); + t.end(); + return; + } + var client = new Raygun.Client().init({apiKey: process.env['RAYGUN_APIKEY']}); client.send(new Error(), {}, function (response) { t.equals(response.statusCode, 202); + t.end(); }); }); -test('send complex', {skip: true}, function (t) { +test('send complex', {}, function (t) { t.plan(1); + + if (semver.satisfies(process.version, '=0.10')) { + t.pass('Ignored on node 0.10'); + t.end(); + return; + } + var client = new Raygun.Client().init({apiKey: process.env['RAYGUN_APIKEY']}).setUser("callum@mindscape.co.nz").setVersion("1.0.0.0"); client.send(new Error(), {}, function (response) { t.equals(response.statusCode, 202); + t.end(); }); }); -test('send with OnBeforeSend', {skip: true}, function (t) { +test('send with OnBeforeSend', {}, function (t) { t.plan(1); + + if (semver.satisfies(process.version, '=0.10')) { + t.pass('Ignored on node 0.10'); + t.end(); + return; + } + var client = new Raygun.Client().init({apiKey: process.env['RAYGUN_APIKEY']}); var onBeforeSendCalled = false; client.onBeforeSend(function (payload) { + onBeforeSendCalled = true; return payload; }); @@ -37,6 +60,22 @@ test('send with OnBeforeSend', {skip: true}, function (t) { }); }); +test('send with expressHandler custom data', function (t) { + t.plan(1); + var client = new Raygun.Client().init({apiKey: process.env['RAYGUN_APIKEY']}); + + client.expressCustomData = function () { + return { 'test': 'data' }; + }; + client._send = client.send; + client.send = function (err, data) { + client.send = client._send; + t.equals(data.test, 'data'); + t.end(); + }; + client.expressHandler(new Error(), {}, {}, function () {}); +}); + test('check that tags get passed through', {}, function (t) { var tag = ['Test']; var client = new Raygun.Client().init({apiKey: 'TEST'});