diff --git a/backend/.env.example b/backend/.env.example index 46e54c35..40df3430 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -11,6 +11,7 @@ SENDGRID_API_KEY="optional, but emails won't send without it" ETHEREUM_ENDPOINT_URI = "http://localhost:8545" CROWD_FUND_URL = "https://eip-712.herokuapp.com/contract/crowd-fund" 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_URL = "http://localhost:5000" # for constructing download url - diff --git a/backend/grant/app.py b/backend/grant/app.py index 18916a25..35628322 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -2,9 +2,12 @@ """The app module, containing the app factory function.""" from flask import Flask 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.extensions import bcrypt, migrate, db, ma, mail, web3 +from grant.settings import SENTRY_RELEASE, ENV def create_app(config_object="grant.settings"): @@ -15,6 +18,11 @@ def create_app(config_object="grant.settings"): register_blueprints(app) register_shellcontext(app) register_commands(app) + sentry_sdk.init( + environment=ENV, + release=SENTRY_RELEASE, + integrations=[FlaskIntegration()] + ) return app diff --git a/backend/grant/settings.py b/backend/grant/settings.py index 96b481c3..210b5642 100644 --- a/backend/grant/settings.py +++ b/backend/grant/settings.py @@ -6,8 +6,11 @@ Most configuration is set via environment variables. For local development, use a .env file to set environment variables. """ +import subprocess from environs import Env +git_revision_short_hash = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']) + env = Env() env.read_env() @@ -29,6 +32,8 @@ SENDGRID_API_KEY = env.str("SENDGRID_API_KEY", default="") SENDGRID_DEFAULT_FROM = "noreply@grant.io" ETHEREUM_PROVIDER = "http" 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_URL = env.str("UPLOAD_URL") MAX_CONTENT_LENGTH = 5 * 1024 * 1024 # 5MB (limits file uploads, raises RequestEntityTooLarge) diff --git a/backend/grant/utils/auth.py b/backend/grant/utils/auth.py index 76e4cf2d..bdf71480 100644 --- a/backend/grant/utils/auth.py +++ b/backend/grant/utils/auth.py @@ -6,6 +6,7 @@ import requests from flask import request, g, jsonify from itsdangerous import SignatureExpired, BadSignature from itsdangerous import TimedJSONWebSignatureSerializer as Serializer +import sentry_sdk from grant.settings import SECRET_KEY, AUTH_URL 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 g.current_user = user + with sentry_sdk.configure_scope() as scope: + scope.user = { + "id": user.id, + } return f(*args, **kwargs) return jsonify(message="Authentication is required to access this resource"), 401 diff --git a/backend/requirements/prod.txt b/backend/requirements/prod.txt index 6e59c149..0e4cb1fa 100644 --- a/backend/requirements/prod.txt +++ b/backend/requirements/prod.txt @@ -60,3 +60,6 @@ flask-yolo2API==0.2.6 #web3 flask-web3==0.1.1 web3==4.8.1 + +#sentry +sentry-sdk[flask]==0.5.5 \ No newline at end of file diff --git a/frontend/.envexample b/frontend/.envexample index 9e4b4151..74862750 100644 --- a/frontend/.envexample +++ b/frontend/.envexample @@ -7,5 +7,11 @@ NO_DEV_TS_CHECK=true # Set the public host url (no trailing slash) 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_FACTORY_URL = "https://eip-712.herokuapp.com/contract/factory" \ No newline at end of file +CROWD_FUND_FACTORY_URL = "https://eip-712.herokuapp.com/contract/factory" + diff --git a/frontend/client/index.tsx b/frontend/client/index.tsx index 1fc82772..ae28e77c 100644 --- a/frontend/client/index.tsx +++ b/frontend/client/index.tsx @@ -6,12 +6,19 @@ import { loadComponents } from 'loadable-components'; import { Provider } from 'react-redux'; import { BrowserRouter as Router } from 'react-router-dom'; import { PersistGate } from 'redux-persist/integration/react'; +import * as Sentry from '@sentry/browser'; import { I18nextProvider } from 'react-i18next'; import { configureStore } from 'store/configure'; import { massageSerializedState } from 'utils/api'; import Routes from './Routes'; import i18n from './i18n'; + +Sentry.init({ + dsn: process.env.SENTRY_DSN, + release: process.env.SENTRY_RELEASE, + environment: process.env.NODE_ENV, +}); const initialState = window && massageSerializedState((window as any).__PRELOADED_STATE__); const { store, persistor } = configureStore(initialState); diff --git a/frontend/client/modules/auth/actions.ts b/frontend/client/modules/auth/actions.ts index 9b39b128..1e803745 100644 --- a/frontend/client/modules/auth/actions.ts +++ b/frontend/client/modules/auth/actions.ts @@ -1,5 +1,6 @@ import types from './types'; import { Dispatch } from 'redux'; +import * as Sentry from '@sentry/browser'; import { sleep } from 'utils/helpers'; import { generateAuthSignatureData } from 'utils/auth'; import { AppState } from 'store/reducers'; @@ -37,7 +38,13 @@ export function authUser(address: string, authSignature?: Falsy | AuthSignatureD signedMessage: authSignature.signedMessage, rawTypedData: JSON.stringify(authSignature.rawTypedData), }); - + // sentry user scope + Sentry.configureScope(scope => { + scope.setUser({ + email: res.data.emailAddress, + accountAddress: res.data.ethAddress, + }); + }); dispatch({ type: types.AUTH_USER_FULFILLED, payload: { diff --git a/frontend/config/env.js b/frontend/config/env.js index be0d9e43..717ad9be 100644 --- a/frontend/config/env.js +++ b/frontend/config/env.js @@ -1,9 +1,15 @@ const fs = require('fs'); const path = require('path'); const paths = require('./paths'); +const childProcess = require('child_process'); delete require.cache[require.resolve('./paths')]; +const gitRevisionShortHash = childProcess + .execSync('git rev-parse --short HEAD') + .toString() + .trim(); + if (!process.env.NODE_ENV) { throw new Error( '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'; } +if (!process.env.SENTRY_RELEASE) { + process.env.SENTRY_RELEASE = gitRevisionShortHash; +} + const appDirectory = fs.realpathSync(process.cwd()); process.env.NODE_PATH = (process.env.NODE_PATH || '') .split(path.delimiter) @@ -67,6 +77,8 @@ module.exports = () => { NODE_ENV: process.env.NODE_ENV || 'development', PORT: process.env.PORT || 3000, 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 diff --git a/frontend/package.json b/frontend/package.json index d4fb59f1..b7ea7db7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -45,6 +45,8 @@ "@babel/register": "^7.0.0", "@ledgerhq/hw-app-eth": "4.23.0", "@ledgerhq/hw-transport-u2f": "4.21.0", + "@sentry/browser": "^4.3.2", + "@sentry/node": "^4.3.2", "@svgr/webpack": "^2.4.0", "@types/classnames": "^2.2.6", "@types/cors": "^2.8.4", diff --git a/frontend/server/index.tsx b/frontend/server/index.tsx index 7e6e4e4d..a7773bd2 100644 --- a/frontend/server/index.tsx +++ b/frontend/server/index.tsx @@ -6,6 +6,7 @@ import manifestHelpers from 'express-manifest-helpers'; import * as bodyParser from 'body-parser'; import expressWinston from 'express-winston'; import i18nMiddleware from 'i18next-express-middleware'; +import * as Sentry from '@sentry/node'; import '../config/env'; // @ts-ignore @@ -17,8 +18,17 @@ import i18n from './i18n'; process.env.SERVER_SIDE_RENDER = 'true'; 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(); +// sentry +app.use(Sentry.Handlers.requestHandler()); + // log requests app.use(expressWinston.logger({ winstonInstance: log })); @@ -59,6 +69,7 @@ app.use( app.use(serverRender()); +app.use(Sentry.Handlers.errorHandler()); app.use(expressWinston.errorLogger({ winstonInstance: log })); app.listen(process.env.PORT || 3000, () => { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1ad8dd0f..8155de23 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1612,6 +1612,60 @@ dependencies: 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": version "4.0.0-alpha.22" 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" 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: version "1.0.2" 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" 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: version "3.1.2" 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" 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" 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" 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: version "1.1.0" 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" 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: version "1.1.4" 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" 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" resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"