Commit 4ebc467c authored by Mike Grabowski's avatar Mike Grabowski Committed by GitHub

feat: implement Sentry for state tracking (#6015)

* feat: add sentry middleware

* chore: remove file

* chore: reorg

* chore: update txt

* chore: fix types

* chore

* chore: normalize

* add todo

* Update src/state/logging.ts
Co-authored-by: default avatarZach Pomerantz <zzmp@uniswap.org>

* Update src/state/logging.ts
Co-authored-by: default avatarZach Pomerantz <zzmp@uniswap.org>

* chore: update type

* nits

---------
Co-authored-by: default avatarZach Pomerantz <zzmp@uniswap.org>
parent 7b7e4e61
import { ApolloClient, ApolloLink, concat, HttpLink, InMemoryCache } from '@apollo/client'
import { SupportedChainId } from 'constants/chains'
import store, { AppState } from '../../state/index'
import store from '../../state/index'
const CHAIN_SUBGRAPH_URL: Record<number, string> = {
[SupportedChainId.MAINNET]: 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3',
......@@ -21,7 +21,7 @@ const httpLink = new HttpLink({ uri: CHAIN_SUBGRAPH_URL[SupportedChainId.MAINNET
// For more information: https://www.apollographql.com/docs/react/networking/advanced-http-networking/
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization to the headers
const chainId = (store.getState() as AppState).application.chainId
const chainId = store.getState().application.chainId
operation.setContext(() => ({
uri:
......
......@@ -37,6 +37,13 @@ if (isSentryEnabled()) {
Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
release: process.env.REACT_APP_GIT_COMMIT_HASH,
/**
* TODO(INFRA-143)
* According to Sentry, this shouldn't be necessary, as they default to `3` when not set.
* Unfortunately, that doesn't work right now, so we workaround it by explicitly setting
* the `normalizeDepth` to `10`. This should be removed once the issue is fixed.
*/
normalizeDepth: 10,
})
}
......
......@@ -4,7 +4,7 @@ import { DEFAULT_TXN_DISMISS_MS } from 'constants/misc'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
import { AppState } from '../index'
import { AppState } from '../types'
import {
addPopup,
ApplicationModal,
......
......@@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { useV2Pair } from '../../hooks/useV2Pairs'
import { useTokenBalances } from '../connection/hooks'
import { AppState } from '../index'
import { AppState } from '../types'
import { Field, typeInput } from './actions'
export function useBurnState(): AppState['burn'] {
......
......@@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
import { PositionDetails } from 'types/position'
import { unwrappedToken } from 'utils/unwrappedToken'
import { AppState } from '../../index'
import { AppState } from '../../types'
import { selectPercent } from './actions'
export function useBurnV3State(): AppState['burnV3'] {
......
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import { AppDispatch, AppState } from 'state'
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector
import store from './index'
export const useAppDispatch = () => useDispatch<typeof store.dispatch>()
export const useAppSelector: TypedUseSelectorHook<ReturnType<typeof store.getState>> = useSelector
import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query/react'
import multicall from 'lib/state/multicall'
import { load, save } from 'redux-localstorage-simple'
import { isTestEnv } from 'utils/env'
import application from './application/reducer'
import burn from './burn/reducer'
import burnV3 from './burn/v3/reducer'
import connection from './connection/reducer'
import { updateVersion } from './global/actions'
import lists from './lists/reducer'
import logs from './logs/slice'
import mint from './mint/reducer'
import mintV3 from './mint/v3/reducer'
import { sentryEnhancer } from './logging'
import reducer from './reducer'
import { routingApi } from './routing/slice'
import swap from './swap/reducer'
import transactions from './transactions/reducer'
import user from './user/reducer'
import wallets from './wallets/reducer'
const PERSISTED_KEYS: string[] = ['user', 'transactions', 'lists']
const store = configureStore({
reducer: {
application,
user,
connection,
transactions,
wallets,
swap,
mint,
mintV3,
burn,
burnV3,
multicall: multicall.reducer,
lists,
logs,
[routingApi.reducerPath]: routingApi.reducer,
},
reducer,
enhancers: (defaultEnhancers) => defaultEnhancers.concat(sentryEnhancer),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ thunk: true })
.concat(routingApi.middleware)
......@@ -50,6 +25,3 @@ store.dispatch(updateVersion())
setupListeners(store.dispatch)
export default store
export type AppState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
......@@ -4,7 +4,7 @@ import { useAppSelector } from 'state/hooks'
import sortByListPriority from 'utils/listSort'
import BROKEN_LIST from '../../constants/tokenLists/broken.tokenlist.json'
import { AppState } from '../index'
import { AppState } from '../types'
import { DEFAULT_ACTIVE_LIST_URLS, UNSUPPORTED_LIST_URLS } from './../../constants/lists'
export type TokenAddressMap = ChainTokenMap
......
import * as Sentry from '@sentry/react'
import { AppState } from './types'
/* Utility type to mark all properties of a type as optional */
type DeepPartial<T> = T extends object
? {
[P in keyof T]?: DeepPartial<T[P]>
}
: T
/**
* This enhancer will automatically store the latest state in Sentry's scope, so that it will be available
* in the Sentry dashboard when an exception happens.
*/
export const sentryEnhancer = Sentry.createReduxEnhancer({
/**
* We don't want to store actions as breadcrumbs in Sentry, so we return null to disable the default behavior.
*/
actionTransformer: () => null,
/**
* We only want to store a subset of the state in Sentry, containing only the relevant parts for debugging.
* Note: This function runs on every state update, so we're keeping it as fast as possible by avoiding any function
* calls and deep object traversals.
*/
stateTransformer: (state: AppState): DeepPartial<AppState> => {
const { application, user, connection, transactions } = state
return {
application: {
fiatOnramp: application.fiatOnramp,
chainId: application.chainId,
openModal: application.openModal,
popupList: application.popupList,
},
user: {
fiatOnrampAcknowledgments: user.fiatOnrampAcknowledgments,
selectedWallet: user.selectedWallet,
lastUpdateVersionTimestamp: user.lastUpdateVersionTimestamp,
matchesDarkMode: user.matchesDarkMode,
userDarkMode: user.userDarkMode,
userLocale: user.userLocale,
userExpertMode: user.userExpertMode,
userClientSideRouter: user.userClientSideRouter,
userHideClosedPositions: user.userHideClosedPositions,
userSlippageTolerance: user.userSlippageTolerance,
userSlippageToleranceHasBeenMigratedToAuto: user.userSlippageToleranceHasBeenMigratedToAuto,
userDeadline: user.userDeadline,
timestamp: user.timestamp,
URLWarningVisible: user.URLWarningVisible,
showSurveyPopup: user.showSurveyPopup,
},
connection: {
errorByConnectionType: connection.errorByConnectionType,
},
transactions,
}
},
})
......@@ -10,7 +10,7 @@ import { useAppDispatch, useAppSelector } from 'state/hooks'
import { useTotalSupply } from '../../hooks/useTotalSupply'
import { PairState, useV2Pair } from '../../hooks/useV2Pairs'
import { useCurrencyBalances } from '../connection/hooks'
import { AppState } from '../index'
import { AppState } from '../types'
import { Field, typeInput } from './actions'
const ZERO = JSBI.BigInt(0)
......
......@@ -24,7 +24,7 @@ import { replaceURLParam } from 'utils/routes'
import { BIG_INT_ZERO } from '../../../constants/misc'
import { PoolState } from '../../../hooks/usePools'
import { useCurrencyBalances } from '../../connection/hooks'
import { AppState } from '../../index'
import { AppState } from '../../types'
import {
Bound,
Field,
......
import multicall from 'lib/state/multicall'
import application from './application/reducer'
import burn from './burn/reducer'
import burnV3 from './burn/v3/reducer'
import connection from './connection/reducer'
import lists from './lists/reducer'
import logs from './logs/slice'
import mint from './mint/reducer'
import mintV3 from './mint/v3/reducer'
import { routingApi } from './routing/slice'
import swap from './swap/reducer'
import transactions from './transactions/reducer'
import user from './user/reducer'
import wallets from './wallets/reducer'
export default {
application,
user,
connection,
transactions,
wallets,
swap,
mint,
mintV3,
burn,
burnV3,
multicall: multicall.reducer,
lists,
logs,
[routingApi.reducerPath]: routingApi.reducer,
}
......@@ -16,7 +16,7 @@ import useENS from '../../hooks/useENS'
import useParsedQueryString from '../../hooks/useParsedQueryString'
import { isAddress } from '../../utils'
import { useCurrencyBalances } from '../connection/hooks'
import { AppState } from '../index'
import { AppState } from '../types'
import { Field, replaceSwapState, selectCurrency, setRecipient, switchCurrencies, typeInput } from './actions'
import { SwapState } from './reducer'
......
import { Reducer } from '@reduxjs/toolkit'
import reducer from './reducer'
/* Utility type to extract state type out of a @reduxjs/toolkit Reducer type */
type GetState<T> = T extends Reducer<infer State> ? State : never
export type AppState = {
[K in keyof typeof reducer]: GetState<typeof reducer[K]>
}
......@@ -13,7 +13,7 @@ import { UserAddedToken } from 'types/tokens'
import { V2_FACTORY_ADDRESSES } from '../../constants/addresses'
import { BASES_TO_TRACK_LIQUIDITY_FOR, PINNED_PAIRS } from '../../constants/routing'
import { useAllTokens } from '../../hooks/Tokens'
import { AppState } from '../index'
import { AppState } from '../types'
import {
addSerializedPair,
addSerializedToken,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment