Skip to content

Commit

Permalink
Add Sentry reporting
Browse files Browse the repository at this point in the history
Errors are reported using `captureException`, and non-errors are added
as breadcrumbs. This means the non-error events won't be directly sent
to Sentry, but they will be included as context for any errors that do
occur.

The sentry configuration is mainly in the `sentry.debug.properties`
file. The only config outside of that file is the DSN used for
reporting errors, which is hard-coded in the `setupSentry.js` module.

There are three separate sets of config: default, debug, and release.
The default config is missing the auth token, so it can't be used to
publish source maps. However, builds that use the default config will
still correctly report errors to the `test-metamask-mobile` Sentry
project.

The debug config is identical to the default config except that it
includes the auth token. The debug config is for publishing any
non-production builds (e.g. testing, pre-releases).

The release configuration is to be used for production builds only.
This is the only config that sends errors to the `metemask-mobile`
Sentry project.

The debug and release config files have not been added to the repo.
They are automatically instantiated from the example files if the
`MM_SENTRY_AUTH_TOKEN` environment variable is set. Setting this
environment variable in CI should allow publishing from CI.

Closes #1321
  • Loading branch information
Gudahtt committed Feb 20, 2020
1 parent 4afbeae commit 3daeba3
Show file tree
Hide file tree
Showing 14 changed files with 415 additions and 40 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,9 @@ coverage
.ios.env
.android.env

# Sentry
/sentry.debug.properties
/sentry.release.properties

# editor
.vscode
8 changes: 8 additions & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ project.ext.react = [

apply from: "../../node_modules/react-native/react.gradle"

project.ext.sentryCli = [
logLevel: "debug",
sentryProperties: System.getenv('SENTRY_PROPERTIES') ? System.getenv('SENTRY_PROPERTIES') : '../../sentry.properties'
]

apply from: "../../node_modules/@sentry/react-native/sentry.gradle"

/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
Expand Down Expand Up @@ -208,6 +215,7 @@ android {
}

dependencies {
implementation project(':@sentry_react-native')
implementation project(':react-native-sensors')
implementation project(':react-native-reanimated')
implementation project(':react-native-webview')
Expand Down
56 changes: 29 additions & 27 deletions android/app/src/main/java/io/metamask/MainApplication.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.metamask;

import com.facebook.react.ReactApplication;
import io.sentry.RNSentryPackage;
import com.sensors.RNSensorsPackage;
import com.swmansion.reanimated.ReanimatedPackage;
import com.reactnativecommunity.webview.RNCWebViewPackage;
Expand Down Expand Up @@ -47,33 +48,34 @@ public boolean getUseDeveloperSupport() {

@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNSensorsPackage(),
new ReanimatedPackage(),
new RNCWebViewPackage(),
new NetInfoPackage(),
new RNViewShotPackage(),
new LottiePackage(),
new AsyncStoragePackage(),
new ReactNativePushNotificationPackage(),
new BackgroundTimerPackage(),
new RNDeviceInfo(),
new SvgPackage(),
new RNGestureHandlerPackage(),
new RNScreensPackage(),
new RNBranchPackage(),
new KeychainPackage(),
new RandomBytesPackage(),
new RCTAesPackage(),
new RNCameraPackage(),
new RNFSPackage(),
new RNI18nPackage(),
new RNOSModule(),
new RNSharePackage(),
new VectorIconsPackage(),
new RCTAnalyticsPackage()
);
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNSentryPackage(),
new RNSensorsPackage(),
new ReanimatedPackage(),
new RNCWebViewPackage(),
new NetInfoPackage(),
new RNViewShotPackage(),
new LottiePackage(),
new AsyncStoragePackage(),
new ReactNativePushNotificationPackage(),
new BackgroundTimerPackage(),
new RNDeviceInfo(),
new SvgPackage(),
new RNGestureHandlerPackage(),
new RNScreensPackage(),
new RNBranchPackage(),
new KeychainPackage(),
new RandomBytesPackage(),
new RCTAesPackage(),
new RNCameraPackage(),
new RNFSPackage(),
new RNI18nPackage(),
new RNOSModule(),
new RNSharePackage(),
new VectorIconsPackage(),
new RCTAnalyticsPackage()
);
}

@Override
Expand Down
2 changes: 2 additions & 0 deletions android/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
rootProject.name = 'MetaMask'
include ':@sentry_react-native'
project(':@sentry_react-native').projectDir = new File(rootProject.projectDir, '../node_modules/@sentry/react-native/android')
include ':react-native-sensors'
project(':react-native-sensors').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sensors/android')
include ':react-native-reanimated'
Expand Down
8 changes: 6 additions & 2 deletions app/util/Logger.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
'use strict';

import { addBreadcrumb, captureException } from '@sentry/react-native';
import AsyncStorage from '@react-native-community/async-storage';

/**
Expand All @@ -21,7 +23,9 @@ export default class Logger {
args.unshift('[MetaMask DEBUG]:');
console.log.apply(null, args); // eslint-disable-line no-console
} else if (metricsOptIn === 'agreed') {
console.log(args);
addBreadcrumb({
message: JSON.stringify(args)
});
}
}

Expand All @@ -38,7 +42,7 @@ export default class Logger {
args.unshift('[MetaMask DEBUG]:');
console.warn(args); // eslint-disable-line no-console
} else if (metricsOptIn === 'agreed') {
console.log(args);
captureException(args);
}
}
}
18 changes: 18 additions & 0 deletions app/util/setupSentry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { init } from '@sentry/react-native';
import { Dedupe, ExtraErrorData } from '@sentry/integrations';

const METAMASK_ENVIRONMENT = process.env['METAMASK_ENVIRONMENT']; // eslint-disable-line dot-notation
const SENTRY_DSN_PROD = 'https://[email protected]/2299799'; // metamask-mobile
const SENTRY_DSN_DEV = 'https://[email protected]/2651591'; // test-metamask-mobile

// Setup sentry remote error reporting
export default function setupSentry() {
const environment = __DEV__ || !METAMASK_ENVIRONMENT ? 'development' : METAMASK_ENVIRONMENT;
const dsn = environment === 'production' ? SENTRY_DSN_PROD : SENTRY_DSN_DEV;
init({
dsn,
debug: __DEV__,
environment,
integrations: [new Dedupe(), new ExtraErrorData()]
});
}
4 changes: 4 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import './shim.js';

import crypto from 'crypto'; // eslint-disable-line import/no-nodejs-modules, no-unused-vars
require('react-native-browser-polyfill'); // eslint-disable-line import/no-commonjs

import setupSentry from './app/util/setupSentry';
setupSentry();

import { AppRegistry, YellowBox } from 'react-native';
import Root from './app/components/Views/Root';
import { name } from './app.json';
Expand Down
47 changes: 45 additions & 2 deletions ios/MetaMask.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
};
objectVersion = 46;
objects = {

/* Begin PBXBuildFile section */
00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; };
00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; };
Expand Down Expand Up @@ -96,6 +95,8 @@
FD9BDCD5059C483EAE9C0160 /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 70BCC86172F14AB2BF4DDA97 /* libRNVectorIcons.a */; };
FEA573B66E7541C5A06F7EE1 /* libRNCAsyncStorage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 512F0EF98E9D4D0185343707 /* libRNCAsyncStorage.a */; };
2A27FC9EEF1F4FD18E658544 /* config.json in Resources */ = {isa = PBXBuildFile; fileRef = EF1C01B7F08047F9B8ADCFBA /* config.json */; };
1F350AF8ECD04C04A76C92C0 /* libRNSentry.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E1888C6B981243669BA00002 /* libRNSentry.a */; };
E3F09DA4B9F147B8811B78BE /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = F19825C44D9D49838B8B39D4 /* libz.tbd */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -853,6 +854,9 @@
F9DFF7AC557B46B6BEFAA1C1 /* libRNShakeEvent.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNShakeEvent.a; sourceTree = "<group>"; };
FE3C9A2458A1416290DEDAD4 /* branch.json */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = branch.json; path = ../branch.json; sourceTree = "<group>"; };
EF1C01B7F08047F9B8ADCFBA /* config.json */ = {isa = PBXFileReference; name = "config.json"; path = "../app/fonts/config.json"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
70988A2A19B140A89BF69948 /* RNSentry.xcodeproj */ = {isa = PBXFileReference; name = "RNSentry.xcodeproj"; path = "../node_modules/@sentry/react-native/ios/RNSentry.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; };
E1888C6B981243669BA00002 /* libRNSentry.a */ = {isa = PBXFileReference; name = "libRNSentry.a"; path = "libRNSentry.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; };
F19825C44D9D49838B8B39D4 /* libz.tbd */ = {isa = PBXFileReference; name = "libz.tbd"; path = "usr/lib/libz.tbd"; sourceTree = SDKROOT; fileEncoding = undefined; lastKnownFileType = sourcecode.text-based-dylib-definition; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -899,6 +903,8 @@
CCD42AFF04EE46C481A5715C /* libRNCWebView.a in Frameworks */,
8F038E7D26A045499C0C2CDC /* libRNReanimated.a in Frameworks */,
0DAA03955C324BF9813B14EE /* libRNSensors.a in Frameworks */,
1F350AF8ECD04C04A76C92C0 /* libRNSentry.a in Frameworks */,
E3F09DA4B9F147B8811B78BE /* libz.tbd in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -1327,6 +1333,7 @@
children = (
153C1A742217BCDC0088EFE0 /* JavaScriptCore.framework */,
2D16E6891FA4F8E400B85C8A /* libReact.a */,
F19825C44D9D49838B8B39D4 /* libz.tbd */,
);
name = Frameworks;
sourceTree = "<group>";
Expand Down Expand Up @@ -1436,6 +1443,7 @@
243DFD20AE7F45D5BBB115E2 /* RNCWebView.xcodeproj */,
3F524C43670B4F7DBBBB4B5C /* RNReanimated.xcodeproj */,
CBAA0A8C106D459EA02E314B /* RNSensors.xcodeproj */,
70988A2A19B140A89BF69948 /* RNSentry.xcodeproj */,
);
name = Libraries;
sourceTree = "<group>";
Expand Down Expand Up @@ -1484,6 +1492,22 @@
name = Products;
sourceTree = "<group>";
};
57D0091455694796AAA21539 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
path = Application;
sourceTree = "<group>";
};
45C45F6CAB7F4405944D88B2 /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
path = Application;
sourceTree = "<group>";
};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
Expand All @@ -1497,6 +1521,7 @@
13B07F8E1A680F5B00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
15ACCA0022655C3A0063978B /* Embed Frameworks */,
A3FF5283B2184432BFF814EE /* Upload Debug Symbols to Sentry */,
);
buildRules = (
);
Expand Down Expand Up @@ -2372,7 +2397,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "#if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n# export SKIP_BUNDLING=true\n#fi\n\nexport NODE_BINARY=${which node}\n../node_modules/react-native/scripts/react-native-xcode.sh\n";
shellScript = "if [ ! -e \"${SENTRY_PROPERTIES}\" ]; then\n export SENTRY_PROPERTIES=../sentry.properties\nfi\n#if [ \"${CONFIGURATION}\" == \"Debug\" ]; then\n# export SKIP_BUNDLING=true\n#fi\n\nexport NODE_BINARY=${which node}\n../node_modules/@sentry/cli/bin/sentry-cli react-native xcode ../node_modules/react-native/scripts/react-native-xcode.sh\n";
};
15FDD86321B76696006B7C35 /* Override xcconfig files */ = {
isa = PBXShellScriptBuildPhase;
Expand All @@ -2392,6 +2417,20 @@
shellPath = /bin/sh;
shellScript = "if [ -e ../.ios.env ]\nthen\n cp -rf ../.ios.env debug.xcconfig\n cp -rf ../.ios.env release.xcconfig\nelse\n cp -rf ../.ios.env.example debug.xcconfig\n cp -rf ../.ios.env.example release.xcconfig\nfi\n\n";
};
A3FF5283B2184432BFF814EE /* Upload Debug Symbols to Sentry */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
name = "Upload Debug Symbols to Sentry";
inputPaths = (
);
outputPaths = (
);
shellPath = /bin/sh;
shellScript = "if [ ! -e \"${SENTRY_PROPERTIES}\" ]; then\n export SENTRY_PROPERTIES=../sentry.properties\nfi\n../node_modules/@sentry/cli/bin/sentry-cli upload-dsym";
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
Expand Down Expand Up @@ -2490,6 +2529,7 @@
"$(SRCROOT)/../node_modules/react-native-webview/ios",
"$(SRCROOT)/../node_modules/react-native-reanimated/ios/**",
"$(SRCROOT)/../node_modules/react-native-sensors/ios/**",
"$(SRCROOT)/../node_modules/@sentry/react-native/ios/**",
);
INFOPLIST_FILE = MetaMask/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
Expand All @@ -2498,6 +2538,7 @@
"$(inherited)",
"\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"",
"\"$(SRCROOT)/MetaMask\"",
"\"$(SRCROOT)/MetaMask\"",
);
LLVM_LTO = YES;
MARKETING_VERSION = 0.2.13;
Expand Down Expand Up @@ -2562,6 +2603,7 @@
"$(SRCROOT)/../node_modules/react-native-webview/ios",
"$(SRCROOT)/../node_modules/react-native-reanimated/ios/**",
"$(SRCROOT)/../node_modules/react-native-sensors/ios/**",
"$(SRCROOT)/../node_modules/@sentry/react-native/ios/**",
);
INFOPLIST_FILE = MetaMask/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
Expand All @@ -2570,6 +2612,7 @@
"$(inherited)",
"\"$(SRCROOT)/MetaMask/System/Library/Frameworks\"",
"\"$(SRCROOT)/MetaMask\"",
"\"$(SRCROOT)/MetaMask\"",
);
LLVM_LTO = YES;
MARKETING_VERSION = 0.2.13;
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
"dependencies": {
"@react-native-community/async-storage": "1.2.0",
"@react-native-community/netinfo": "4.1.5",
"@sentry/integrations": "^5.11.1",
"@sentry/react-native": "1.2.2",
"@tradle/react-native-http": "2.0.1",
"@walletconnect/react-native": "1.0.0-beta.37",
"asyncstorage-down": "4.2.0",
Expand Down Expand Up @@ -273,7 +275,7 @@
"^.+\\.js$": "<rootDir>jest.preprocessor.js"
},
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|@react-navigation|@react-native-community|react-navigation|react-navigation-redux-helpers)"
"node_modules/(?!(jest-)?react-native|@react-navigation|@react-native-community|react-navigation|react-navigation-redux-helpers|@sentry)"
],
"snapshotSerializers": [
"enzyme-to-json/serializer"
Expand Down
32 changes: 32 additions & 0 deletions scripts/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,38 @@ checkParameters "$@"

printTitle

if [ "$MODE" == "release" ]; then
if [ "$PRE_RELEASE" = false ]; then
if [ ! -e ./sentry.release.properties ]; then
if [ -n "${MM_SENTRY_AUTH_TOKEN}" ]; then
cp ./sentry.release.properties.example ./sentry.release.properties
else
printError "Missing 'sentry.release.properties' file (see 'sentry.release.properties.example')"
exit 1
fi
fi
if [ -n "${MM_SENTRY_AUTH_TOKEN}" ]; then
sed -i'' -e "s/auth.token.*/auth.token=${MM_SENTRY_AUTH_TOKEN}/" ./sentry.release.properties;
fi
export SENTRY_PROPERTIES='../../sentry.release.properties'
export METAMASK_ENVIRONMENT='production'
else
if [ ! -e ./sentry.debug.properties ]; then
if [ -n "${MM_SENTRY_AUTH_TOKEN}" ]; then
cp ./sentry.debug.properties.example ./sentry.debug.properties
else
printError "Missing 'sentry.debug.properties' file (see 'sentry.debug.properties.example')"
exit 1
fi
fi
if [ -n "${MM_SENTRY_AUTH_TOKEN}" ]; then
sed -i'' -e "s/auth.token.*/auth.token=${MM_SENTRY_AUTH_TOKEN}/" ./sentry.debug.properties;
fi
export SENTRY_PROPERTIES='../../sentry.debug.properties'
export METAMASK_ENVIRONMENT='prerelease'
fi
fi

if [ "$PLATFORM" == "ios" ]; then
# we don't care about env file in CI
if [ -f "$IOS_ENV_FILE" ] || [ "$CI" = true ]; then
Expand Down
5 changes: 5 additions & 0 deletions sentry.debug.properties.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defaults.url=https://sentry.io/
defaults.org=metamask
defaults.project=test-metamask-mobile
auth.token=
cli.executable=node_modules/@sentry/cli/bin/sentry-cli
5 changes: 5 additions & 0 deletions sentry.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defaults.url=https://sentry.io/
defaults.org=metamask
defaults.project=test-metamask-mobile
auth.token=
cli.executable=node_modules/@sentry/cli/bin/sentry-cli
5 changes: 5 additions & 0 deletions sentry.release.properties.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defaults.url=https://sentry.io/
defaults.org=metamask
defaults.project=metamask-mobile
auth.token=
cli.executable=node_modules/@sentry/cli/bin/sentry-cli
Loading

0 comments on commit 3daeba3

Please sign in to comment.