Sentry Integration (#221)

* BE sentry setup w/ user scope

* FE sentry integration + user scope

* FE env adjustments

* FE: use NODE_ENV for Sentry

* BE: use FLASK_ENV for Sentry

* BE: remove email, acct & ip from Sentry user scope

* comment .env.example SENTRY* for CI

* fix merge artifact
This commit is contained in:
AMStrix 2018-11-21 23:45:29 -06:00 committed by Daniel Ternyak
parent 968974d8d7
commit 7abeac7bd7
12 changed files with 146 additions and 5 deletions

View File

@ -11,6 +11,7 @@ SENDGRID_API_KEY="optional, but emails won't send without it"
ETHEREUM_ENDPOINT_URI = "http://localhost:8545" ETHEREUM_ENDPOINT_URI = "http://localhost:8545"
CROWD_FUND_URL = "https://eip-712.herokuapp.com/contract/crowd-fund" CROWD_FUND_URL = "https://eip-712.herokuapp.com/contract/crowd-fund"
CROWD_FUND_FACTORY_URL = "https://eip-712.herokuapp.com/contract/factory" CROWD_FUND_FACTORY_URL = "https://eip-712.herokuapp.com/contract/factory"
# SENTRY_DSN="https://PUBLICKEY@sentry.io/PROJECTID"
# SENTRY_RELEASE="optional, overrides git hash"
UPLOAD_DIRECTORY = "/tmp" UPLOAD_DIRECTORY = "/tmp"
UPLOAD_URL = "http://localhost:5000" # for constructing download url UPLOAD_URL = "http://localhost:5000" # for constructing download url

View File

@ -2,9 +2,12 @@
"""The app module, containing the app factory function.""" """The app module, containing the app factory function."""
from flask import Flask from flask import Flask
from flask_cors import CORS from flask_cors import CORS
from sentry_sdk.integrations.flask import FlaskIntegration
import sentry_sdk
from grant import commands, proposal, user, comment, milestone, admin, email from grant import commands, proposal, user, comment, milestone, admin, email
from grant.extensions import bcrypt, migrate, db, ma, mail, web3 from grant.extensions import bcrypt, migrate, db, ma, mail, web3
from grant.settings import SENTRY_RELEASE, ENV
def create_app(config_object="grant.settings"): def create_app(config_object="grant.settings"):
@ -15,6 +18,11 @@ def create_app(config_object="grant.settings"):
register_blueprints(app) register_blueprints(app)
register_shellcontext(app) register_shellcontext(app)
register_commands(app) register_commands(app)
sentry_sdk.init(
environment=ENV,
release=SENTRY_RELEASE,
integrations=[FlaskIntegration()]
)
return app return app

View File

@ -6,8 +6,11 @@ Most configuration is set via environment variables.
For local development, use a .env file to set For local development, use a .env file to set
environment variables. environment variables.
""" """
import subprocess
from environs import Env from environs import Env
git_revision_short_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])
env = Env() env = Env()
env.read_env() env.read_env()
@ -29,6 +32,8 @@ SENDGRID_API_KEY = env.str("SENDGRID_API_KEY", default="")
SENDGRID_DEFAULT_FROM = "noreply@grant.io" SENDGRID_DEFAULT_FROM = "noreply@grant.io"
ETHEREUM_PROVIDER = "http" ETHEREUM_PROVIDER = "http"
ETHEREUM_ENDPOINT_URI = env.str("ETHEREUM_ENDPOINT_URI") ETHEREUM_ENDPOINT_URI = env.str("ETHEREUM_ENDPOINT_URI")
SENTRY_DSN = env.str("SENTRY_DSN", default=None)
SENTRY_RELEASE = env.str("SENTRY_RELEASE", default=git_revision_short_hash)
UPLOAD_DIRECTORY = env.str("UPLOAD_DIRECTORY") UPLOAD_DIRECTORY = env.str("UPLOAD_DIRECTORY")
UPLOAD_URL = env.str("UPLOAD_URL") UPLOAD_URL = env.str("UPLOAD_URL")
MAX_CONTENT_LENGTH = 5 * 1024 * 1024 # 5MB (limits file uploads, raises RequestEntityTooLarge) MAX_CONTENT_LENGTH = 5 * 1024 * 1024 # 5MB (limits file uploads, raises RequestEntityTooLarge)

View File

@ -6,6 +6,7 @@ import requests
from flask import request, g, jsonify from flask import request, g, jsonify
from itsdangerous import SignatureExpired, BadSignature from itsdangerous import SignatureExpired, BadSignature
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
import sentry_sdk
from grant.settings import SECRET_KEY, AUTH_URL from grant.settings import SECRET_KEY, AUTH_URL
from ..proposal.models import Proposal from ..proposal.models import Proposal
@ -69,6 +70,10 @@ def requires_sm(f):
return jsonify(message="No user exists with address: {}".format(auth_address)), 401 return jsonify(message="No user exists with address: {}".format(auth_address)), 401
g.current_user = user g.current_user = user
with sentry_sdk.configure_scope() as scope:
scope.user = {
"id": user.id,
}
return f(*args, **kwargs) return f(*args, **kwargs)
return jsonify(message="Authentication is required to access this resource"), 401 return jsonify(message="Authentication is required to access this resource"), 401

View File

@ -60,3 +60,6 @@ flask-yolo2API==0.2.6
#web3 #web3
flask-web3==0.1.1 flask-web3==0.1.1
web3==4.8.1 web3==4.8.1
#sentry
sentry-sdk[flask]==0.5.5

View File

@ -7,5 +7,11 @@ NO_DEV_TS_CHECK=true
# Set the public host url (no trailing slash) # Set the public host url (no trailing slash)
PUBLIC_HOST_URL=https://demo.grant.io PUBLIC_HOST_URL=https://demo.grant.io
# sentry
SENTRY_DSN="https://PUBLICKEY@sentry.io/PROJECTID"
SENTRY_RELEASE="optional, overrides git hash"
CROWD_FUND_URL = "https://eip-712.herokuapp.com/contract/crowd-fund" CROWD_FUND_URL = "https://eip-712.herokuapp.com/contract/crowd-fund"
CROWD_FUND_FACTORY_URL = "https://eip-712.herokuapp.com/contract/factory" CROWD_FUND_FACTORY_URL = "https://eip-712.herokuapp.com/contract/factory"

View File

@ -6,12 +6,19 @@ import { loadComponents } from 'loadable-components';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
import { PersistGate } from 'redux-persist/integration/react'; import { PersistGate } from 'redux-persist/integration/react';
import * as Sentry from '@sentry/browser';
import { I18nextProvider } from 'react-i18next'; import { I18nextProvider } from 'react-i18next';
import { configureStore } from 'store/configure'; import { configureStore } from 'store/configure';
import { massageSerializedState } from 'utils/api'; import { massageSerializedState } from 'utils/api';
import Routes from './Routes'; import Routes from './Routes';
import i18n from './i18n'; import i18n from './i18n';
Sentry.init({
dsn: process.env.SENTRY_DSN,
release: process.env.SENTRY_RELEASE,
environment: process.env.NODE_ENV,
});
const initialState = const initialState =
window && massageSerializedState((window as any).__PRELOADED_STATE__); window && massageSerializedState((window as any).__PRELOADED_STATE__);
const { store, persistor } = configureStore(initialState); const { store, persistor } = configureStore(initialState);

View File

@ -1,5 +1,6 @@
import types from './types'; import types from './types';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
import * as Sentry from '@sentry/browser';
import { sleep } from 'utils/helpers'; import { sleep } from 'utils/helpers';
import { generateAuthSignatureData } from 'utils/auth'; import { generateAuthSignatureData } from 'utils/auth';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
@ -37,7 +38,13 @@ export function authUser(address: string, authSignature?: Falsy | AuthSignatureD
signedMessage: authSignature.signedMessage, signedMessage: authSignature.signedMessage,
rawTypedData: JSON.stringify(authSignature.rawTypedData), rawTypedData: JSON.stringify(authSignature.rawTypedData),
}); });
// sentry user scope
Sentry.configureScope(scope => {
scope.setUser({
email: res.data.emailAddress,
accountAddress: res.data.ethAddress,
});
});
dispatch({ dispatch({
type: types.AUTH_USER_FULFILLED, type: types.AUTH_USER_FULFILLED,
payload: { payload: {

View File

@ -1,9 +1,15 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const paths = require('./paths'); const paths = require('./paths');
const childProcess = require('child_process');
delete require.cache[require.resolve('./paths')]; delete require.cache[require.resolve('./paths')];
const gitRevisionShortHash = childProcess
.execSync('git rev-parse --short HEAD')
.toString()
.trim();
if (!process.env.NODE_ENV) { if (!process.env.NODE_ENV) {
throw new Error( throw new Error(
'The process.env.NODE_ENV environment variable is required but was not specified.', 'The process.env.NODE_ENV environment variable is required but was not specified.',
@ -54,6 +60,10 @@ if (!process.env.BACKEND_URL) {
process.env.BACKEND_URL = 'http://localhost:5000'; process.env.BACKEND_URL = 'http://localhost:5000';
} }
if (!process.env.SENTRY_RELEASE) {
process.env.SENTRY_RELEASE = gitRevisionShortHash;
}
const appDirectory = fs.realpathSync(process.cwd()); const appDirectory = fs.realpathSync(process.cwd());
process.env.NODE_PATH = (process.env.NODE_PATH || '') process.env.NODE_PATH = (process.env.NODE_PATH || '')
.split(path.delimiter) .split(path.delimiter)
@ -67,6 +77,8 @@ module.exports = () => {
NODE_ENV: process.env.NODE_ENV || 'development', NODE_ENV: process.env.NODE_ENV || 'development',
PORT: process.env.PORT || 3000, PORT: process.env.PORT || 3000,
PUBLIC_HOST_URL: process.env.PUBLIC_HOST_URL, PUBLIC_HOST_URL: process.env.PUBLIC_HOST_URL,
SENTRY_DSN: process.env.SENTRY_DSN || null,
SENTRY_RELEASE: process.env.SENTRY_RELEASE,
}; };
// Stringify all values so we can feed into Webpack DefinePlugin // Stringify all values so we can feed into Webpack DefinePlugin

View File

@ -45,6 +45,8 @@
"@babel/register": "^7.0.0", "@babel/register": "^7.0.0",
"@ledgerhq/hw-app-eth": "4.23.0", "@ledgerhq/hw-app-eth": "4.23.0",
"@ledgerhq/hw-transport-u2f": "4.21.0", "@ledgerhq/hw-transport-u2f": "4.21.0",
"@sentry/browser": "^4.3.2",
"@sentry/node": "^4.3.2",
"@svgr/webpack": "^2.4.0", "@svgr/webpack": "^2.4.0",
"@types/classnames": "^2.2.6", "@types/classnames": "^2.2.6",
"@types/cors": "^2.8.4", "@types/cors": "^2.8.4",

View File

@ -6,6 +6,7 @@ import manifestHelpers from 'express-manifest-helpers';
import * as bodyParser from 'body-parser'; import * as bodyParser from 'body-parser';
import expressWinston from 'express-winston'; import expressWinston from 'express-winston';
import i18nMiddleware from 'i18next-express-middleware'; import i18nMiddleware from 'i18next-express-middleware';
import * as Sentry from '@sentry/node';
import '../config/env'; import '../config/env';
// @ts-ignore // @ts-ignore
@ -17,8 +18,17 @@ import i18n from './i18n';
process.env.SERVER_SIDE_RENDER = 'true'; process.env.SERVER_SIDE_RENDER = 'true';
const isDev = process.env.NODE_ENV === 'development'; const isDev = process.env.NODE_ENV === 'development';
Sentry.init({
dsn: process.env.SENTRY_DSN,
release: process.env.SENTRY_RELEASE,
environment: process.env.NODE_ENV,
});
const app = express(); const app = express();
// sentry
app.use(Sentry.Handlers.requestHandler());
// log requests // log requests
app.use(expressWinston.logger({ winstonInstance: log })); app.use(expressWinston.logger({ winstonInstance: log }));
@ -59,6 +69,7 @@ app.use(
app.use(serverRender()); app.use(serverRender());
app.use(Sentry.Handlers.errorHandler());
app.use(expressWinston.errorLogger({ winstonInstance: log })); app.use(expressWinston.errorLogger({ winstonInstance: log }));
app.listen(process.env.PORT || 3000, () => { app.listen(process.env.PORT || 3000, () => {

View File

@ -1612,6 +1612,60 @@
dependencies: dependencies:
any-observable "^0.3.0" any-observable "^0.3.0"
"@sentry/browser@^4.3.2":
version "4.3.2"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-4.3.2.tgz#430b83583c5c25d33041dd80bf6ed19216086f70"
dependencies:
"@sentry/core" "4.3.2"
"@sentry/types" "4.3.2"
"@sentry/utils" "4.3.2"
"@sentry/core@4.3.2":
version "4.3.2"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.3.2.tgz#e8a2850a11316d865ed7d3030ee2c4a1608bb1d8"
dependencies:
"@sentry/hub" "4.3.2"
"@sentry/minimal" "4.3.2"
"@sentry/types" "4.3.2"
"@sentry/utils" "4.3.2"
"@sentry/hub@4.3.2":
version "4.3.2"
resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.3.2.tgz#1ea10038e2080035d2bc09f5f26829cd106a1516"
dependencies:
"@sentry/types" "4.3.2"
"@sentry/utils" "4.3.2"
"@sentry/minimal@4.3.2":
version "4.3.2"
resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.3.2.tgz#c0958e5858b2105a6a0b523787e459a03af603cc"
dependencies:
"@sentry/hub" "4.3.2"
"@sentry/types" "4.3.2"
"@sentry/node@^4.3.2":
version "4.3.2"
resolved "https://registry.yarnpkg.com/@sentry/node/-/node-4.3.2.tgz#3c7cd3aff238f3b1eb3252147b963cbdf520aa45"
dependencies:
"@sentry/core" "4.3.2"
"@sentry/hub" "4.3.2"
"@sentry/types" "4.3.2"
"@sentry/utils" "4.3.2"
cookie "0.3.1"
lsmod "1.0.0"
md5 "2.2.1"
stack-trace "0.0.10"
"@sentry/types@4.3.2":
version "4.3.2"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.3.2.tgz#28b143979482fcbc9f9e520250482dde015b13fa"
"@sentry/utils@4.3.2":
version "4.3.2"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.3.2.tgz#de14046eba972af9d62508f78cd998b0352d634a"
dependencies:
"@sentry/types" "4.3.2"
"@storybook/addons@4.0.0-alpha.22": "@storybook/addons@4.0.0-alpha.22":
version "4.0.0-alpha.22" version "4.0.0-alpha.22"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-4.0.0-alpha.22.tgz#08d89396fff216c0d5aa305f7ac851b6bc34b6cf" resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-4.0.0-alpha.22.tgz#08d89396fff216c0d5aa305f7ac851b6bc34b6cf"
@ -4105,6 +4159,10 @@ chardet@^0.7.0:
version "0.7.0" version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
charenc@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
check-error@^1.0.1: check-error@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
@ -4812,6 +4870,10 @@ cross-spawn@^3.0.0:
lru-cache "^4.0.1" lru-cache "^4.0.1"
which "^1.2.9" which "^1.2.9"
crypt@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
cryptiles@3.x.x: cryptiles@3.x.x:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
@ -8082,7 +8144,7 @@ is-boolean-object@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
is-buffer@^1.0.2, is-buffer@^1.1.5: is-buffer@^1.0.2, is-buffer@^1.1.5, is-buffer@~1.1.1:
version "1.1.6" version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
@ -9658,6 +9720,10 @@ lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
pseudomap "^1.0.2" pseudomap "^1.0.2"
yallist "^2.1.2" yallist "^2.1.2"
lsmod@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b"
make-dir@^1.0.0: make-dir@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51"
@ -9751,6 +9817,14 @@ md5.js@^1.3.4:
hash-base "^3.0.0" hash-base "^3.0.0"
inherits "^2.0.1" inherits "^2.0.1"
md5@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
dependencies:
charenc "~0.0.1"
crypt "~0.0.1"
is-buffer "~1.1.1"
mdn-data@~1.1.0: mdn-data@~1.1.0:
version "1.1.4" version "1.1.4"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01"
@ -14230,7 +14304,7 @@ stable@~0.1.6:
version "0.1.8" version "0.1.8"
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
stack-trace@0.0.x: stack-trace@0.0.10, stack-trace@0.0.x:
version "0.0.10" version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"