Log errors to Sentry (#1422)

This commit is contained in:
Marcin Mielnicki
2018-03-24 20:30:51 +01:00
committed by Thaddée Tyl
parent ea4b758612
commit fe4ac0bf1c
7 changed files with 142 additions and 7 deletions

View File

@@ -131,10 +131,12 @@ SVG or JSON output. When deliberately changing the output, run
`SNAPSHOT_DRY=1 npm run test:js:server` to preview changes to the saved
snapshots, and `SNAPSHOT_UPDATE=1 npm run test:js:server` to update them.
The server can be [configured][sentry configuration] to use [Sentry][sentry].
[package manager]: https://nodejs.org/en/download/package-manager/
[snapshot tests]: https://glebbahmutov.com/blog/snapshot-testing/
[sentry configuration]: doc/self-hosting.md#sentry
[Sentry]: https://sentry.io/
Hosting your own server
-----------------------

View File

@@ -176,3 +176,26 @@ Set the `REDIRECT_URI` environment variable:
```sh
REDIRECT_URI=http://my-custom-shields.s3.amazonaws.com/
```
Sentry
------
In order to enable integration with [Sentry](https://sentry.io), you need your own [Sentry DSN](https://docs.sentry.io/quickstart/#configure-the-dsn). Its an URL in format `https://{PUBLIC_KEY}:{SECRET_KEY}@sentry.io/{PROJECT_ID}`.
### How to obtain the Sentry DSN
1. [Sign up](https://sentry.io/pricing/) for Sentry
1. Log in to Sentry
1. Create a new project for Node.js
1. You should see [Sentry DSN](https://docs.sentry.io/quickstart/#configure-the-dsn) for your project. Sentry DSN can be found by navigating to \[Project Name] -> Project Settings -> Client Keys (DSN) as well.
Start the server using the Sentry DSN. You can set it:
- by `SENTRY_DSN` environment variable
```
SENTRY_DSN=https://xxx:yyy@sentry.io/zzz sudo node server
```
- or by `sentry_dsn` secret property defined in `private/secret.json`
```
sudo node server
```

View File

@@ -1,4 +1,6 @@
'use strict';
const Raven = require('raven');
const throttle = require('lodash.throttle');
const listeners = [];
@@ -26,9 +28,16 @@ module.exports = function log(...msg) {
console.log(d, ...msg);
};
const throttledConsoleError = throttle(console.error, 10000, { trailing: false });
module.exports.error = function error(...msg) {
const d = date();
listeners.forEach(f => f(d, ...msg))
listeners.forEach(f => f(d, ...msg));
Raven.captureException(msg, function (sendErr) {
if (sendErr) {
throttledConsoleError('Failed to send captured exception to Sentry', sendErr.message);
}
});
console.error(d, ...msg);
};

48
lib/log.spec.js Normal file
View File

@@ -0,0 +1,48 @@
'use strict';
const chai = require('chai');
const expect = chai.expect;
const sinon = require('sinon');
const sinonChai = require('sinon-chai');;
const Raven = require('raven');
chai.use(sinonChai);
function requireUncached(module){
delete require.cache[require.resolve(module)]
return require(module)
}
describe('log', () => {
describe('error', () => {
before(() => {
this.clock = sinon.useFakeTimers();
sinon.stub(Raven, 'captureException').callsFake(function fakeFn(e, callback) {
callback(new Error(`Cannot send message "${e}" to Sentry.`));
});
// we have to create a spy before requiring 'error' function
sinon.spy(console, 'error');
this.error = requireUncached('./log').error;
});
after(() => {
this.clock.restore();
console.error.restore();
Raven.captureException.restore();
});
it('should throttle errors from Raven client', () => {
this.error('test error 1');
this.error('test error 2');
this.error('test error 3');
this.clock.tick(11000);
this.error('test error 4');
this.error('test error 5');
expect(console.error).to.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 1" to Sentry.');
expect(console.error).to.not.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 2" to Sentry.');
expect(console.error).to.not.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 3" to Sentry.');
expect(console.error).to.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 4" to Sentry.');
expect(console.error).to.not.be.calledWithExactly('Failed to send captured exception to Sentry', 'Cannot send message "test error 5" to Sentry.');
});
});
});

55
package-lock.json generated
View File

@@ -2429,6 +2429,11 @@
"integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=",
"dev": true
},
"charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
},
"check-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
@@ -3245,6 +3250,11 @@
"which": "1.3.0"
}
},
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
},
"cryptiles": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
@@ -6908,8 +6918,7 @@
"is-buffer": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz",
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=",
"dev": true
"integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw="
},
"is-builtin-module": {
"version": "1.0.0",
@@ -8021,6 +8030,11 @@
"integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=",
"dev": true
},
"lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
},
"lodash.toarray": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz",
@@ -8134,6 +8148,16 @@
"minimatch": "3.0.4"
}
},
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
"integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
"requires": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "1.1.5"
}
},
"md5-file": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/md5-file/-/md5-file-3.2.3.tgz",
@@ -10268,6 +10292,25 @@
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
"dev": true
},
"raven": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/raven/-/raven-2.4.2.tgz",
"integrity": "sha1-ASnircMHiGRv1TC2fQioziXU9tw=",
"requires": {
"cookie": "0.3.1",
"md5": "2.2.1",
"stack-trace": "0.0.9",
"timed-out": "4.0.1",
"uuid": "3.0.0"
},
"dependencies": {
"uuid": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz",
"integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg="
}
}
},
"rc": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.5.tgz",
@@ -11575,6 +11618,11 @@
"safe-buffer": "5.1.1"
}
},
"stack-trace": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz",
"integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU="
},
"stackframe": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.0.4.tgz",
@@ -12338,8 +12386,7 @@
"timed-out": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
"integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=",
"dev": true
"integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8="
},
"timers-browserify": {
"version": "2.0.6",

View File

@@ -31,12 +31,14 @@
"jsonpath": "~1.0.0",
"lodash.countby": "^4.6.0",
"lodash.mapkeys": "^4.6.0",
"lodash.throttle": "^4.1.1",
"lodash.uniq": "~4.5.0",
"moment": "^2.19.3",
"node-env-flag": "^0.1.0",
"pdfkit": "~0.8.0",
"pretty-bytes": "^4.0.2",
"query-string": "^6.0.0",
"raven": "^2.4.2",
"redis": "~2.6.2",
"request": "~2.85.0",
"semver": "~5.5.0",

View File

@@ -10,6 +10,11 @@ const queryString = require('query-string');
const semver = require('semver');
const xml2js = require('xml2js');
const xpath = require('xpath');
const Raven = require('raven');
const serverSecrets = require('./lib/server-secrets');
Raven.config(process.env.SENTRY_DSN || serverSecrets.sentry_dsn).install();
Raven.disableConsoleAlerts();
const { isDeprecated, getDeprecatedBadge } = require('./lib/deprecation-helpers');
const { checkErrorResponse } = require('./lib/error-helper');
@@ -20,7 +25,6 @@ const sysMonitor = require('./lib/sys/monitor');
const log = require('./lib/log');
const { makeMakeBadgeFn } = require('./lib/make-badge');
const { QuickTextMeasurer } = require('./lib/text-measurer');
const serverSecrets = require('./lib/server-secrets');
const suggest = require('./lib/suggest');
const {licenseToColor} = require('./lib/licenses');
const { latest: latestVersion } = require('./lib/version');