Merge pull request #1654 from balena-io/1523-update-sentry

Replace Raven SDK with "new" Sentry "Unified" SDK (fix "CLI prints 'null' and exits")
This commit is contained in:
Paulo Castro 2020-03-10 11:52:48 -03:00 committed by GitHub
commit 08f40c0566
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 202 additions and 124 deletions

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2019 Balena Ltd.
* Copyright 2019-2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -15,6 +15,8 @@
* limitations under the License.
*/
import * as packageJSON from '../package.json';
class CliSettings {
public readonly settings: any;
constructor() {
@ -42,30 +44,26 @@ class CliSettings {
/**
* Sentry.io setup
* @see https://docs.sentry.io/clients/node/
* @see https://docs.sentry.io/error-reporting/quickstart/?platform=node
*/
function setupRaven() {
const Raven = require('raven');
Raven.disableConsoleAlerts();
Raven.config(require('./config').sentryDsn, {
captureUnhandledRejections: true,
autoBreadcrumbs: true,
release: require('../package.json').version,
}).install(function(_logged: any, error: Error) {
console.error(error);
process.exit(1);
async function setupSentry() {
const config = await import('./config');
const Sentry = await import('@sentry/node');
Sentry.init({
dsn: config.sentryDsn,
release: packageJSON.version,
});
Raven.setContext({
extra: {
args: process.argv,
Sentry.configureScope(scope => {
scope.setExtras({
is_pkg: !!(process as any).pkg,
node_version: process.version,
},
platform: process.platform,
});
});
}
function checkNodeVersion() {
const validNodeVersions = require('../package.json').engines.node;
const validNodeVersions = packageJSON.engines.node;
if (!require('semver').satisfies(process.version, validNodeVersions)) {
const { stripIndent } = require('common-tags');
console.warn(stripIndent`
@ -244,7 +242,7 @@ export function setMaxListeners(maxListeners: number) {
}
export async function globalInit() {
setupRaven();
await setupSentry();
checkNodeVersion();
configureBluebird();

View File

@ -1,5 +1,5 @@
/*
Copyright 2016-2019 Balena
Copyright 2016-2020 Balena
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -14,21 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import * as Bluebird from 'bluebird';
import { stripIndent } from 'common-tags';
import * as _ from 'lodash';
import * as os from 'os';
import * as Raven from 'raven';
import { TypedError } from 'typed-error';
export class ExpectedError extends Error {}
export class ExpectedError extends TypedError {}
export class NotLoggedInError extends ExpectedError {}
const captureException = Bluebird.promisify<string, Error>(
Raven.captureException,
{ context: Raven },
);
function hasCode(error: any): error is Error & { code: string } {
return error.code != null;
}
@ -122,16 +116,25 @@ export async function handleError(error: any) {
}
printErrorMessage(message.join('\n'));
if (error instanceof ExpectedError) {
const expectedErrorREs = [
/^BalenaApplicationNotFound:/, // balena-sdk
/^BalenaDeviceNotFound:/, // balena-sdk
/^Missing \w+$/, // Capitano's command line parsing error
/^Unexpected arguments?:/, // oclif's command line parsing error
];
if (
error instanceof ExpectedError ||
expectedErrorREs.some(re => re.test(message[0]))
) {
return;
}
// Report "unexpected" errors via Sentry.io
await captureException(error)
.timeout(1000)
.catch(function() {
// Ignore any errors (from error logging, or timeouts)
})
// exit with the process.exitCode set earlier
.finally(() => process.exit());
const Sentry = await import('@sentry/node');
Sentry.captureException(error);
await Sentry.close(1000);
// Unhandled/unexpected error: ensure that the process terminates.
// The exit error code was set above through `process.exitCode`.
process.exit();
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2019 Balena Ltd.
* Copyright 2019-2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,12 +14,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Promise = require('bluebird');
import _ = require('lodash');
import Mixpanel = require('mixpanel');
import Raven = require('raven');
import * as Sentry from '@sentry/node';
import * as Bluebird from 'bluebird';
import * as _ from 'lodash';
import * as Mixpanel from 'mixpanel';
import packageJSON = require('../package.json');
import * as packageJSON from '../package.json';
import { getBalenaSdk } from './utils/lazy';
const getMixpanel = _.once<any>(() => {
@ -31,36 +31,43 @@ const getMixpanel = _.once<any>(() => {
});
});
/**
* Mixpanel.com analytics tracking (information on balena CLI usage).
*
* @param commandSignature A string like, for example:
* "push <applicationOrDevice>"
* That's literally so: "applicationOrDevice" is NOT replaced with the actual
* application ID or device ID. The purpose is to find out the most / least
* used command verbs, so we can focus our development effort where it is most
* beneficial to end users.
*
* The username and command signature are also added as extra context
* information in Sentry.io error reporting, for CLI debugging purposes
* (mainly unexpected/unhandled exceptions -- see also `lib/errors.ts`).
*/
export function trackCommand(commandSignature: string) {
const balena = getBalenaSdk();
return Promise.props({
return Bluebird.props({
balenaUrl: balena.settings.get('balenaUrl'),
username: balena.auth.whoami().catchReturn(undefined),
mixpanel: getMixpanel(),
})
.then(({ username, balenaUrl, mixpanel }) => {
return Promise.try(() => {
Raven.mergeContext({
user: {
id: username,
username,
},
});
// commandSignature is a string like, for example:
// "push <applicationOrDevice>"
// That's literally so: "applicationOrDevice" is NOT replaced with
// the actual application ID or device ID. The purpose is find out the
// most / least used command verbs, so we can focus our development
// effort where it is most beneficial to end users.
return mixpanel.track(`[CLI] ${commandSignature}`, {
distinct_id: username,
version: packageJSON.version,
node: process.version,
arch: process.arch,
balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com'
platform: process.platform,
Sentry.configureScope(scope => {
scope.setExtra('command', commandSignature);
scope.setUser({
id: username,
username,
});
});
return mixpanel.track(`[CLI] ${commandSignature}`, {
distinct_id: username,
version: packageJSON.version,
node: process.version,
arch: process.arch,
balenaUrl, // e.g. 'balena-cloud.com' or 'balena-staging.com'
platform: process.platform,
});
})
.timeout(100)
.catchReturn(undefined);

5
lib/global.d.ts vendored
View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2019 Balena Ltd.
* Copyright 2019-2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,6 +20,9 @@ interface Dictionary<T> {
}
declare module '*/package.json' {
export const engines: {
node: string;
};
export const name: string;
export const version: string;
}

View File

@ -1,6 +1,6 @@
/**
* @license
* Copyright 2019 Balena Ltd.
* Copyright 2019-2020 Balena Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,8 +16,12 @@
*/
import { Hook } from '@oclif/config';
let trackResolve: (result: Promise<any>) => void;
// note: trackPromise is subject to a Bluebird.timeout, defined in events.ts
export let trackPromise: PromiseLike<void>;
export const trackPromise = new Promise(resolve => {
trackResolve = resolve;
});
/**
* This is an oclif 'prerun' hook. This hook runs after the command line is
@ -38,7 +42,7 @@ const hook: Hook<'prerun'> = async function(options) {
// Intentionally do not await for the track promise here, in order to
// run the command tracking and the command itself in parallel.
trackPromise = events.trackCommand(cmdSignature);
trackResolve(events.trackCommand(cmdSignature));
};
export default hook;

View File

@ -22,12 +22,12 @@ import { RegistrySecrets } from 'resin-multibuild';
import * as Stream from 'stream';
import streamToPromise = require('stream-to-promise');
import { Pack } from 'tar-stream';
import { TypedError } from 'typed-error';
import Logger = require('./logger');
import { ExpectedError } from '../errors';
import { exitWithExpectedError } from '../utils/patterns';
import { tarDirectory } from './compose';
import { getVisuals } from './lazy';
import Logger = require('./logger');
const globalLogger = Logger.getLogger();
@ -77,7 +77,7 @@ interface HeadlessBuilderMessage {
releaseId?: number;
}
export class RemoteBuildFailedError extends TypedError {
export class RemoteBuildFailedError extends ExpectedError {
public constructor(message = 'Remote build failed') {
super(message);
}
@ -138,10 +138,10 @@ export async function startRemoteBuild(build: RemoteBuild): Promise<void> {
stream.on('end', resolve);
stream.on('error', reject);
}).then(() => {
globalLogger.outputDeferredMessages();
if (build.hadError) {
throw new RemoteBuildFailedError();
}
globalLogger.outputDeferredMessages();
});
}

163
npm-shrinkwrap.json generated
View File

@ -553,6 +553,113 @@
"resolved": "https://registry.npmjs.org/@resin.io/valid-email/-/valid-email-0.1.0.tgz",
"integrity": "sha1-DnUwmoQ8AUqAqhSC+WmQYvL6UV0="
},
"@sentry/apm": {
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/@sentry/apm/-/apm-5.13.2.tgz",
"integrity": "sha512-Pv6PRVkcmmYYIT422gXm968F8YQyf5uN1RSHOFBjWsxI3Ke/uRgeEdIVKPDo78GklBfETyRN6GyLEZ555jRe6g==",
"requires": {
"@sentry/browser": "5.13.2",
"@sentry/hub": "5.13.2",
"@sentry/minimal": "5.13.2",
"@sentry/types": "5.13.2",
"@sentry/utils": "5.13.2",
"tslib": "^1.9.3"
}
},
"@sentry/browser": {
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.13.2.tgz",
"integrity": "sha512-4MeauHs8Rf1c2FF6n84wrvA4LexEL1K/Tg3r+1vigItiqyyyYBx1sPjHGZeKeilgBi+6IEV5O8sy30QIrA/NsQ==",
"requires": {
"@sentry/core": "5.13.2",
"@sentry/types": "5.13.2",
"@sentry/utils": "5.13.2",
"tslib": "^1.9.3"
}
},
"@sentry/core": {
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.13.2.tgz",
"integrity": "sha512-iB7CQSt9e0EJhSmcNOCjzJ/u7E7qYJ3mI3h44GO83n7VOmxBXKSvtUl9FpKFypbWrsdrDz8HihLgAZZoMLWpPA==",
"requires": {
"@sentry/hub": "5.13.2",
"@sentry/minimal": "5.13.2",
"@sentry/types": "5.13.2",
"@sentry/utils": "5.13.2",
"tslib": "^1.9.3"
}
},
"@sentry/hub": {
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.13.2.tgz",
"integrity": "sha512-/U7yq3DTuRz8SRpZVKAaenW9sD2F5wbj12kDVPxPnGspyqhy0wBWKs9j0YJfBiDXMKOwp3HX964O3ygtwjnfAw==",
"requires": {
"@sentry/types": "5.13.2",
"@sentry/utils": "5.13.2",
"tslib": "^1.9.3"
}
},
"@sentry/minimal": {
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.13.2.tgz",
"integrity": "sha512-VV0eA3HgrnN3mac1XVPpSCLukYsU+QxegbmpnZ8UL8eIQSZ/ZikYxagDNlZbdnmXHUpOEUeag2gxVntSCo5UcA==",
"requires": {
"@sentry/hub": "5.13.2",
"@sentry/types": "5.13.2",
"tslib": "^1.9.3"
}
},
"@sentry/node": {
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.13.2.tgz",
"integrity": "sha512-LwNOUvc0+28jYfI0o4HmkDTEYdY3dWvSCnL5zggO12buon7Wc+jirXZbEQAx84HlXu7sGSjtKCTzUQOphv7sPw==",
"requires": {
"@sentry/apm": "5.13.2",
"@sentry/core": "5.13.2",
"@sentry/hub": "5.13.2",
"@sentry/types": "5.13.2",
"@sentry/utils": "5.13.2",
"cookie": "^0.3.1",
"https-proxy-agent": "^4.0.0",
"lru_map": "^0.3.3",
"tslib": "^1.9.3"
},
"dependencies": {
"agent-base": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
"integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g=="
},
"cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
},
"https-proxy-agent": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz",
"integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==",
"requires": {
"agent-base": "5",
"debug": "4"
}
}
}
},
"@sentry/types": {
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.13.2.tgz",
"integrity": "sha512-mgAEQyc77PYBnAjnslSXUz6aKgDlunlg2c2qSK/ivKlEkTgTWWW/dE76++qVdrqM8SupnqQoiXyPDL0wUNdB3g=="
},
"@sentry/utils": {
"version": "5.13.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.13.2.tgz",
"integrity": "sha512-LwPQl6WRMKEnd16kg35HS3yE+VhBc8vN4+BBIlrgs7X0aoT+AbEd/sQLMisDgxNboCF44Ho3RCKtztiPb9blqg==",
"requires": {
"@sentry/types": "5.13.2",
"tslib": "^1.9.3"
}
},
"@sinonjs/commons": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.0.tgz",
@ -905,15 +1012,6 @@
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
"dev": true
},
"@types/raven": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/@types/raven/-/raven-2.5.3.tgz",
"integrity": "sha512-k6vxiX5I6/GEqJS9mMYPVIgMJf/X26n09NfzuqBpdcEp684RIwpdrwCgSyJGuy8EaSG1wc2rFP7xVEAixPzw7Q==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/request": {
"version": "2.48.4",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz",
@ -2850,11 +2948,6 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"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",
@ -3630,11 +3723,6 @@
}
}
},
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
},
"crypto-random-string": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz",
@ -8523,6 +8611,11 @@
"es5-ext": "~0.10.2"
}
},
"lru_map": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz",
"integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0="
},
"lzma-native": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lzma-native/-/lzma-native-4.0.6.tgz",
@ -8697,16 +8790,6 @@
"chs": "^1.1.0"
}
},
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
"integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
"requires": {
"charenc": "~0.0.1",
"crypt": "~0.0.1",
"is-buffer": "~1.1.1"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -14814,25 +14897,6 @@
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
},
"raven": {
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/raven/-/raven-2.6.4.tgz",
"integrity": "sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw==",
"requires": {
"cookie": "0.3.1",
"md5": "^2.2.1",
"stack-trace": "0.0.10",
"timed-out": "4.0.1",
"uuid": "3.3.2"
},
"dependencies": {
"cookie": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
}
}
},
"raw-body": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
@ -16884,7 +16948,8 @@
"stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA="
"integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
"dev": true
},
"static-extend": {
"version": "0.1.2",

View File

@ -25,8 +25,7 @@
"scripts": [
"node_modules/balena-sync/build/capitano/*.js",
"node_modules/balena-sync/build/sync/*.js",
"node_modules/resin-compose-parse/build/schemas/*.json",
"node_modules/raven/lib/instrumentation/*.js"
"node_modules/resin-compose-parse/build/schemas/*.json"
],
"assets": [
"build/**/*.js",
@ -121,7 +120,6 @@
"@types/nock": "^11.0.7",
"@types/node": "^10.17.17",
"@types/prettyjson": "0.0.29",
"@types/raven": "^2.5.3",
"@types/request": "^2.48.4",
"@types/rewire": "^2.5.28",
"@types/rimraf": "^2.0.3",
@ -160,6 +158,7 @@
"@oclif/command": "^1.5.19",
"@oclif/errors": "^1.2.2",
"@resin.io/valid-email": "^0.1.0",
"@sentry/node": "^5.13.1",
"@zeit/dockerignore": "0.0.3",
"JSONStream": "^1.0.3",
"ansi-escapes": "^2.0.0",
@ -221,7 +220,6 @@
"patch-package": "6.1.2",
"prettyjson": "^1.1.3",
"progress-stream": "^2.0.0",
"raven": "^2.5.0",
"reconfix": "^0.1.0",
"request": "^2.88.2",
"resin-cli-form": "^2.0.1",