Commit 5ba45dbb authored by tom goriunov's avatar tom goriunov Committed by GitHub

Migrate from Sentry.io to Rollbar monitoring solution (#2403)

* basic Rollbar setup

* remove sentry code

* add rollback env to values for demo

* [skip ci] adjust instance name

* fix validator

* don't log errors from API by default and change error levels

* [skip ci] change default value for environment property

* [skip ci] do not log api errors of address endpoint
parent f086f33c
NEXT_PUBLIC_SENTRY_DSN=https://sentry.io NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN=xxx
SENTRY_CSP_REPORT_URI=https://sentry.io
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=xxx NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=xxx
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X
......
# TODO @tom2drum setup source maps for Rollbar
name: Upload source maps to Sentry name: Upload source maps to Sentry
on: on:
workflow_call: workflow_call:
......
...@@ -43,9 +43,6 @@ yarn-error.log* ...@@ -43,9 +43,6 @@ yarn-error.log*
.eslintcache .eslintcache
# Sentry
.sentryclirc
**.decrypted~** **.decrypted~**
/test-results/ /test-results/
/playwright-report/ /playwright-report/
......
...@@ -25,10 +25,10 @@ export { default as nameService } from './nameService'; ...@@ -25,10 +25,10 @@ export { default as nameService } from './nameService';
export { default as publicTagsSubmission } from './publicTagsSubmission'; export { default as publicTagsSubmission } from './publicTagsSubmission';
export { default as restApiDocs } from './restApiDocs'; export { default as restApiDocs } from './restApiDocs';
export { default as rewards } from './rewards'; export { default as rewards } from './rewards';
export { default as rollbar } from './rollbar';
export { default as rollup } from './rollup'; export { default as rollup } from './rollup';
export { default as safe } from './safe'; export { default as safe } from './safe';
export { default as saveOnGas } from './saveOnGas'; export { default as saveOnGas } from './saveOnGas';
export { default as sentry } from './sentry';
export { default as sol2uml } from './sol2uml'; export { default as sol2uml } from './sol2uml';
export { default as stats } from './stats'; export { default as stats } from './stats';
export { default as suave } from './suave'; export { default as suave } from './suave';
......
...@@ -3,35 +3,34 @@ import type { Feature } from './types'; ...@@ -3,35 +3,34 @@ import type { Feature } from './types';
import app from '../app'; import app from '../app';
import { getEnvValue } from '../utils'; import { getEnvValue } from '../utils';
const dsn = getEnvValue('NEXT_PUBLIC_SENTRY_DSN'); const clientToken = getEnvValue('NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN');
const instance = (() => { const instance = (() => {
const envValue = getEnvValue('NEXT_PUBLIC_APP_INSTANCE'); const envValue = getEnvValue('NEXT_PUBLIC_APP_INSTANCE');
if (envValue) { if (envValue) {
return envValue; return envValue;
} }
return app.host?.replace('.blockscout.com', '').replaceAll('-', '_'); return app.host?.replace('.blockscout.com', '').replace('.k8s-dev', '').replaceAll('-', '_');
})(); })();
const environment = getEnvValue('NEXT_PUBLIC_APP_ENV') || 'production'; const environment = getEnvValue('NEXT_PUBLIC_APP_ENV') || 'production';
const release = getEnvValue('NEXT_PUBLIC_GIT_TAG'); const codeVersion = getEnvValue('NEXT_PUBLIC_GIT_TAG') || getEnvValue('NEXT_PUBLIC_GIT_COMMIT_SHA');
const title = 'Sentry error monitoring';
const title = 'Rollbar error monitoring';
const config: Feature<{ const config: Feature<{
dsn: string; clientToken: string;
instance: string;
release: string | undefined;
environment: string; environment: string;
enableTracing: boolean; instance: string | undefined;
codeVersion: string | undefined;
}> = (() => { }> = (() => {
if (dsn && instance && environment) { if (clientToken) {
return Object.freeze({ return Object.freeze({
title, title,
isEnabled: true, isEnabled: true,
dsn, clientToken,
instance,
release,
environment, environment,
enableTracing: getEnvValue('NEXT_PUBLIC_SENTRY_ENABLE_TRACING') === 'true', instance,
codeVersion,
}); });
} }
......
...@@ -61,7 +61,6 @@ NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://eth.drpc.org?ref=559183','text':'Public ...@@ -61,7 +61,6 @@ NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://eth.drpc.org?ref=559183','text':'Public
NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://merits.blockscout.com NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://merits.blockscout.com
NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-mainnet.safe.global
NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true
NEXT_PUBLIC_SENTRY_ENABLE_TRACING=true
NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
......
...@@ -64,7 +64,6 @@ NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-c ...@@ -64,7 +64,6 @@ NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-c
NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}] NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}]
NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://merits.blockscout.com NEXT_PUBLIC_REWARDS_SERVICE_API_HOST=https://merits.blockscout.com
NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global
NEXT_PUBLIC_SENTRY_ENABLE_TRACING=true
NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s.blockscout.com NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=noves NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=noves
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
......
...@@ -7,6 +7,7 @@ NEXT_PUBLIC_APP_PROTOCOL=http ...@@ -7,6 +7,7 @@ NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000 NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_APP_INSTANCE=rubber_duck
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
# Instance ENVs # Instance ENVs
...@@ -60,7 +61,6 @@ NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true ...@@ -60,7 +61,6 @@ NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/sepolia-testnet.png NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/sepolia-testnet.png
NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}] NEXT_PUBLIC_OTHER_LINKS=[{'url':'https://sepolia.drpc.org?ref=559183','text':'Public RPC'}]
NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global NEXT_PUBLIC_SAFE_TX_SERVICE_URL=https://safe-transaction-sepolia.safe.global
NEXT_PUBLIC_SENTRY_ENABLE_TRACING=true
NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true NEXT_PUBLIC_SEO_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-dev.blockscout.com NEXT_PUBLIC_STATS_API_HOST=https://stats-sepolia.k8s-dev.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
......
...@@ -147,6 +147,16 @@ function printDeprecationWarning(envsMap: Record<string, string>) { ...@@ -147,6 +147,16 @@ function printDeprecationWarning(envsMap: Record<string, string>) {
console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n'); console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n');
} }
if (
(envsMap.NEXT_PUBLIC_SENTRY_DSN || envsMap.SENTRY_CSP_REPORT_URI || envsMap.NEXT_PUBLIC_SENTRY_ENABLE_TRACING) &&
envsMap.NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN
) {
console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗');
// eslint-disable-next-line max-len
console.warn('The Sentry monitoring is now deprecated and will be removed in the next release. Please migrate to the Rollbar error monitoring.');
console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n');
}
if ( if (
envsMap.NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR || envsMap.NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR ||
envsMap.NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND envsMap.NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND
...@@ -178,5 +188,14 @@ function checkDeprecatedEnvs(envsMap: Record<string, string>) { ...@@ -178,5 +188,14 @@ function checkDeprecatedEnvs(envsMap: Record<string, string>) {
throw new Error(); throw new Error();
} }
if (
(envsMap.NEXT_PUBLIC_SENTRY_DSN || envsMap.SENTRY_CSP_REPORT_URI || envsMap.NEXT_PUBLIC_SENTRY_ENABLE_TRACING) &&
!envsMap.NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN
) {
// eslint-disable-next-line max-len
console.log('🚨 The Sentry error monitoring is no longer supported. Please migrate to the Rollbar error monitoring.');
throw new Error();
}
!silent && console.log('👍 All good!\n'); !silent && console.log('👍 All good!\n');
} }
...@@ -372,6 +372,7 @@ const adsBannerSchema = yup ...@@ -372,6 +372,7 @@ const adsBannerSchema = yup
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: adButlerConfigSchema, NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE: adButlerConfigSchema,
}); });
// DEPRECATED
const sentrySchema = yup const sentrySchema = yup
.object() .object()
.shape({ .shape({
...@@ -389,20 +390,6 @@ const sentrySchema = yup ...@@ -389,20 +390,6 @@ const sentrySchema = yup
is: (value: string) => Boolean(value), is: (value: string) => Boolean(value),
then: (schema) => schema, then: (schema) => schema,
}), }),
NEXT_PUBLIC_APP_INSTANCE: yup
.string()
.when('NEXT_PUBLIC_SENTRY_DSN', {
is: (value: string) => Boolean(value),
then: (schema) => schema,
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_APP_INSTANCE cannot not be used without NEXT_PUBLIC_SENTRY_DSN'),
}),
NEXT_PUBLIC_APP_ENV: yup
.string()
.when('NEXT_PUBLIC_SENTRY_DSN', {
is: (value: string) => Boolean(value),
then: (schema) => schema,
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_APP_ENV cannot not be used without NEXT_PUBLIC_SENTRY_DSN'),
}),
}); });
const accountSchema = yup const accountSchema = yup
...@@ -592,6 +579,8 @@ const schema = yup ...@@ -592,6 +579,8 @@ const schema = yup
NEXT_PUBLIC_APP_HOST: yup.string().required(), NEXT_PUBLIC_APP_HOST: yup.string().required(),
NEXT_PUBLIC_APP_PROTOCOL: yup.string().oneOf(protocols), NEXT_PUBLIC_APP_PROTOCOL: yup.string().oneOf(protocols),
NEXT_PUBLIC_APP_PORT: yup.number().positive().integer(), NEXT_PUBLIC_APP_PORT: yup.number().positive().integer(),
NEXT_PUBLIC_APP_ENV: yup.string(),
NEXT_PUBLIC_APP_INSTANCE: yup.string(),
// 2. Blockchain parameters // 2. Blockchain parameters
NEXT_PUBLIC_NETWORK_NAME: yup.string().required(), NEXT_PUBLIC_NETWORK_NAME: yup.string().required(),
...@@ -898,6 +887,7 @@ const schema = yup ...@@ -898,6 +887,7 @@ const schema = yup
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: yup.string(), NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: yup.string(),
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: yup.string(), NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: yup.string(),
NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: yup.string(), NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: yup.string(),
NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN: yup.string(),
// Misc // Misc
NEXT_PUBLIC_USE_NEXT_JS_PROXY: yup.boolean(), NEXT_PUBLIC_USE_NEXT_JS_PROXY: yup.boolean(),
......
NEXT_PUBLIC_SENTRY_DSN=https://sentry.io NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN=https://rollbar.com
NEXT_PUBLIC_AUTH_URL=https://example.com NEXT_PUBLIC_AUTH_URL=https://example.com
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_LOGOUT_URL=https://example.com NEXT_PUBLIC_LOGOUT_URL=https://example.com
......
NEXT_PUBLIC_SENTRY_DSN=https://sentry.io
SENTRY_CSP_REPORT_URI=https://sentry.io
NEXT_PUBLIC_SENTRY_ENABLE_TRACING=true
NEXT_PUBLIC_APP_ENV=production
NEXT_PUBLIC_APP_INSTANCE=duck
\ No newline at end of file
...@@ -47,8 +47,7 @@ frontend: ...@@ -47,8 +47,7 @@ frontend:
memory: 384Mi memory: 384Mi
cpu: 250m cpu: 250m
env: env:
NEXT_PUBLIC_APP_ENV: development NEXT_PUBLIC_APP_ENV: review
NEXT_PUBLIC_APP_INSTANCE: review_L2
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/base.svg
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/base.svg
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-mainnet.json NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/base-mainnet.json
...@@ -73,8 +72,6 @@ frontend: ...@@ -73,8 +72,6 @@ frontend:
NEXT_PUBLIC_NAVIGATION_LAYOUT: horizontal NEXT_PUBLIC_NAVIGATION_LAYOUT: horizontal
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/blocks','/name-domains']" NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/blocks','/name-domains']"
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID
......
...@@ -47,8 +47,7 @@ frontend: ...@@ -47,8 +47,7 @@ frontend:
memory: 384Mi memory: 384Mi
cpu: 250m cpu: 250m
env: env:
NEXT_PUBLIC_APP_ENV: development NEXT_PUBLIC_APP_ENV: review
NEXT_PUBLIC_APP_INSTANCE: review
NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-sepolia.json NEXT_PUBLIC_FEATURED_NETWORKS: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth-sepolia.json
NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg NEXT_PUBLIC_NETWORK_LOGO: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg
NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png NEXT_PUBLIC_NETWORK_ICON: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/sepolia.png
...@@ -79,11 +78,10 @@ frontend: ...@@ -79,11 +78,10 @@ frontend:
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']" NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES: "['/apps']"
PROMETHEUS_METRICS_ENABLED: true PROMETHEUS_METRICS_ENABLED: true
envFromSecret: envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID
NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY
NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN
NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY
NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN
...@@ -60,6 +60,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -60,6 +60,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
- [MetaSuites extension](ENVS.md#metasuites-extension) - [MetaSuites extension](ENVS.md#metasuites-extension)
- [Validators list](ENVS.md#validators-list) - [Validators list](ENVS.md#validators-list)
- [Sentry error monitoring](ENVS.md#sentry-error-monitoring) - [Sentry error monitoring](ENVS.md#sentry-error-monitoring)
- [Rollbar error monitoring](ENVS.md#rollbar-error-monitoring)
- [OpenTelemetry](ENVS.md#opentelemetry) - [OpenTelemetry](ENVS.md#opentelemetry)
- [DeFi dropdown](ENVS.md#defi-dropdown) - [DeFi dropdown](ENVS.md#defi-dropdown)
- [Multichain balance button](ENVS.md#multichain-balance-button) - [Multichain balance button](ENVS.md#multichain-balance-button)
...@@ -77,6 +78,8 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will ...@@ -77,6 +78,8 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
| NEXT_PUBLIC_APP_PROTOCOL | `http \| https` | App url schema | - | `https` | `http` | v1.0.x+ | | NEXT_PUBLIC_APP_PROTOCOL | `http \| https` | App url schema | - | `https` | `http` | v1.0.x+ |
| NEXT_PUBLIC_APP_HOST | `string` | App host | Required | - | `blockscout.com` | v1.0.x+ | | NEXT_PUBLIC_APP_HOST | `string` | App host | Required | - | `blockscout.com` | v1.0.x+ |
| NEXT_PUBLIC_APP_PORT | `number` | Port where app is running | - | `3000` | `3001` | v1.0.x+ | | NEXT_PUBLIC_APP_PORT | `number` | Port where app is running | - | `3000` | `3001` | v1.0.x+ |
| NEXT_PUBLIC_APP_ENV | `string` | App env (e.g development, staging, production, etc.). | - | `production` | `staging` | v1.0.x+ |
| NEXT_PUBLIC_APP_INSTANCE | `string` | Name of app instance. Used for app monitoring purposes. If not provided, it will be constructed from `NEXT_PUBLIC_APP_HOST` | - | - | `wonderful_kepler` | v1.0.x+ |
| NEXT_PUBLIC_USE_NEXT_JS_PROXY | `boolean` | Tells the app to proxy all APIs request through the NextJS app. **We strongly advise not to use it in the production environment**, since it can lead to performance issues of the NodeJS server | - | `false` | `true` | v1.8.0+ | | NEXT_PUBLIC_USE_NEXT_JS_PROXY | `boolean` | Tells the app to proxy all APIs request through the NextJS app. **We strongly advise not to use it in the production environment**, since it can lead to performance issues of the NodeJS server | - | `false` | `true` | v1.8.0+ |
&nbsp; &nbsp;
...@@ -738,13 +741,21 @@ The feature enables the Validators page which provides detailed information abou ...@@ -738,13 +741,21 @@ The feature enables the Validators page which provides detailed information abou
### Sentry error monitoring ### Sentry error monitoring
_Note_ This feature is **deprecated**. All ENV variables will be removed in the future releases.
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_SENTRY_DSN | `string` | Client key for your Sentry.io app | Required | - | `<your-secret>` | v1.0.x+ | | NEXT_PUBLIC_SENTRY_DSN | `string` | Client key for your Sentry.io app | Required | - | `<your-secret>` | v1.0.x+ |
| SENTRY_CSP_REPORT_URI | `string` | URL for sending CSP-reports to your Sentry.io app | - | - | `<your-secret>` | v1.0.x+ | | SENTRY_CSP_REPORT_URI | `string` | URL for sending CSP-reports to your Sentry.io app | - | - | `<your-secret>` | v1.0.x+ |
| NEXT_PUBLIC_SENTRY_ENABLE_TRACING | `boolean` | Enables tracing and performance monitoring in Sentry.io | - | `false` | `true` | v1.17.0+ | | NEXT_PUBLIC_SENTRY_ENABLE_TRACING | `boolean` | Enables tracing and performance monitoring in Sentry.io | - | `false` | `true` | v1.17.0+ |
| NEXT_PUBLIC_APP_ENV | `string` | App env (e.g development, review or production). Passed as `environment` property to Sentry config | - | `production` | `production` | v1.0.x+ |
| NEXT_PUBLIC_APP_INSTANCE | `string` | Name of app instance. Used as custom tag `app_instance` value in the main Sentry scope. If not provided, it will be constructed from `NEXT_PUBLIC_APP_HOST` | - | - | `wonderful_kepler` | v1.0.x+ | &nbsp;
### Rollbar error monitoring
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_ROLLBAR_CLIENT_TOKEN | `string` | Client token for your Rollbar project | Required | - | `<your-secret>` | v1.37.x+ |
&nbsp; &nbsp;
......
...@@ -21,6 +21,7 @@ export interface Params<R extends ResourceName> { ...@@ -21,6 +21,7 @@ export interface Params<R extends ResourceName> {
pathParams?: ResourcePathParams<R>; pathParams?: ResourcePathParams<R>;
queryParams?: Record<string, string | Array<string> | number | boolean | undefined | null>; queryParams?: Record<string, string | Array<string> | number | boolean | undefined | null>;
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'signal' | 'headers'>; fetchParams?: Pick<FetchParams, 'body' | 'method' | 'signal' | 'headers'>;
logError?: boolean;
} }
export default function useApiFetch() { export default function useApiFetch() {
...@@ -30,7 +31,7 @@ export default function useApiFetch() { ...@@ -30,7 +31,7 @@ export default function useApiFetch() {
return React.useCallback(<R extends ResourceName, SuccessType = unknown, ErrorType = unknown>( return React.useCallback(<R extends ResourceName, SuccessType = unknown, ErrorType = unknown>(
resourceName: R, resourceName: R,
{ pathParams, queryParams, fetchParams }: Params<R> = {}, { pathParams, queryParams, fetchParams, logError }: Params<R> = {},
) => { ) => {
const apiToken = cookies.get(cookies.NAMES.API_TOKEN); const apiToken = cookies.get(cookies.NAMES.API_TOKEN);
...@@ -58,7 +59,7 @@ export default function useApiFetch() { ...@@ -58,7 +59,7 @@ export default function useApiFetch() {
}, },
{ {
resource: resource.path, resource: resource.path,
omitSentryErrorLog: true, // disable logging of API errors to Sentry logError,
}, },
); );
}, [ fetch, csrfToken ]); }, [ fetch, csrfToken ]);
......
...@@ -11,6 +11,7 @@ export interface Params<R extends ResourceName, E = unknown, D = ResourcePayload ...@@ -11,6 +11,7 @@ export interface Params<R extends ResourceName, E = unknown, D = ResourcePayload
queryParams?: Record<string, string | Array<string> | number | boolean | undefined>; queryParams?: Record<string, string | Array<string> | number | boolean | undefined>;
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'headers'>; fetchParams?: Pick<FetchParams, 'body' | 'method' | 'headers'>;
queryOptions?: Partial<Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, D>, 'queryFn'>>; queryOptions?: Partial<Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, D>, 'queryFn'>>;
logError?: boolean;
} }
export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) { export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) {
...@@ -23,7 +24,7 @@ export function getResourceKey<R extends ResourceName>(resource: R, { pathParams ...@@ -23,7 +24,7 @@ export function getResourceKey<R extends ResourceName>(resource: R, { pathParams
export default function useApiQuery<R extends ResourceName, E = unknown, D = ResourcePayload<R>>( export default function useApiQuery<R extends ResourceName, E = unknown, D = ResourcePayload<R>>(
resource: R, resource: R,
{ queryOptions, pathParams, queryParams, fetchParams }: Params<R, E, D> = {}, { queryOptions, pathParams, queryParams, fetchParams, logError }: Params<R, E, D> = {},
) { ) {
const apiFetch = useApiFetch(); const apiFetch = useApiFetch();
...@@ -33,7 +34,7 @@ export default function useApiQuery<R extends ResourceName, E = unknown, D = Res ...@@ -33,7 +34,7 @@ export default function useApiQuery<R extends ResourceName, E = unknown, D = Res
// all errors and error typing is handled by react-query // all errors and error typing is handled by react-query
// so error response will never go to the data // so error response will never go to the data
// that's why we are safe here to do type conversion "as Promise<ResourcePayload<R>>" // that's why we are safe here to do type conversion "as Promise<ResourcePayload<R>>"
return apiFetch(resource, { pathParams, queryParams, fetchParams: { ...fetchParams, signal } }) as Promise<ResourcePayload<R>>; return apiFetch(resource, { pathParams, queryParams, logError, fetchParams: { ...fetchParams, signal } }) as Promise<ResourcePayload<R>>;
}, },
...queryOptions, ...queryOptions,
}); });
......
...@@ -27,7 +27,7 @@ export default function useAddressProfileApiQuery(hash: string | undefined, isEn ...@@ -27,7 +27,7 @@ export default function useAddressProfileApiQuery(hash: string | undefined, isEn
return Promise.reject(); return Promise.reject();
} }
return fetch(feature.apiUrlTemplate.replace('{address}', hash), undefined, { omitSentryErrorLog: true }); return fetch(feature.apiUrlTemplate.replace('{address}', hash));
}, },
enabled: isEnabled && Boolean(hash), enabled: isEnabled && Boolean(hash),
refetchOnMount: false, refetchOnMount: false,
......
import * as Sentry from '@sentry/react';
import React from 'react'; import React from 'react';
import isBodyAllowed from 'lib/api/isBodyAllowed'; import isBodyAllowed from 'lib/api/isBodyAllowed';
import type { ResourceError, ResourcePath } from 'lib/api/resources'; import type { ResourceError, ResourcePath } from 'lib/api/resources';
import { useRollbar } from 'lib/rollbar';
export interface Params { export interface Params {
method?: RequestInit['method']; method?: RequestInit['method'];
...@@ -14,10 +14,12 @@ export interface Params { ...@@ -14,10 +14,12 @@ export interface Params {
interface Meta { interface Meta {
resource?: ResourcePath; resource?: ResourcePath;
omitSentryErrorLog?: boolean; logError?: boolean;
} }
export default function useFetch() { export default function useFetch() {
const rollbar = useRollbar();
return React.useCallback(<Success, Error>(path: string, params?: Params, meta?: Meta): Promise<Success | ResourceError<Error>> => { return React.useCallback(<Success, Error>(path: string, params?: Params, meta?: Meta): Promise<Success | ResourceError<Error>> => {
const _body = params?.body; const _body = params?.body;
const isFormData = _body instanceof FormData; const isFormData = _body instanceof FormData;
...@@ -51,13 +53,12 @@ export default function useFetch() { ...@@ -51,13 +53,12 @@ export default function useFetch() {
statusText: response.statusText, statusText: response.statusText,
}; };
if (!meta?.omitSentryErrorLog) { if (meta?.logError && rollbar) {
Sentry.captureException(new Error('Client fetch failed'), { tags: { rollbar.warn('Client fetch failed', {
source: 'fetch', resource: meta?.resource,
'source.resource': meta?.resource, status_code: error.status,
'status.code': error.status, status_text: error.statusText,
'status.text': error.statusText, });
} });
} }
return response.json().then( return response.json().then(
...@@ -75,5 +76,5 @@ export default function useFetch() { ...@@ -75,5 +76,5 @@ export default function useFetch() {
return response.json() as Promise<Success>; return response.json() as Promise<Success>;
} }
}); });
}, [ ]); }, [ rollbar ]);
} }
import * as Sentry from '@sentry/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import buildUrl from 'lib/api/buildUrl'; import buildUrl from 'lib/api/buildUrl';
...@@ -6,9 +5,11 @@ import isNeedProxy from 'lib/api/isNeedProxy'; ...@@ -6,9 +5,11 @@ import isNeedProxy from 'lib/api/isNeedProxy';
import { getResourceKey } from 'lib/api/useApiQuery'; import { getResourceKey } from 'lib/api/useApiQuery';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import { useRollbar } from 'lib/rollbar';
export default function useGetCsrfToken() { export default function useGetCsrfToken() {
const nodeApiFetch = useFetch(); const nodeApiFetch = useFetch();
const rollbar = useRollbar();
return useQuery({ return useQuery({
queryKey: getResourceKey('csrf'), queryKey: getResourceKey('csrf'),
...@@ -19,12 +20,11 @@ export default function useGetCsrfToken() { ...@@ -19,12 +20,11 @@ export default function useGetCsrfToken() {
const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf'); const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf');
if (!csrfFromHeader) { if (!csrfFromHeader) {
Sentry.captureException(new Error('Client fetch failed'), { tags: { rollbar?.warn('Client fetch failed', {
source: 'fetch', resource: 'csrf',
'source.resource': 'csrf', status_code: 500,
'status.code': 500, status_text: 'Unable to obtain csrf token from header',
'status.text': 'Unable to obtain csrf token from header', });
} });
return; return;
} }
......
...@@ -15,7 +15,7 @@ export default function useIsSafeAddress(hash: string | undefined): boolean { ...@@ -15,7 +15,7 @@ export default function useIsSafeAddress(hash: string | undefined): boolean {
return Promise.reject(); return Promise.reject();
} }
return fetch(`${ feature.apiUrl }/${ hash }`, undefined, { omitSentryErrorLog: true }); return fetch(`${ feature.apiUrl }/${ hash }`);
}, },
enabled: feature.isEnabled && Boolean(hash), enabled: feature.isEnabled && Boolean(hash),
refetchOnMount: false, refetchOnMount: false,
......
import { Provider as DefaultProvider, useRollbar as useRollbarDefault } from '@rollbar/react';
import type React from 'react';
import type { Configuration } from 'rollbar';
import config from 'configs/app';
const feature = config.features.rollbar;
const FallbackProvider = ({ children }: { children: React.ReactNode }) => children;
const useRollbarFallback = (): undefined => {};
export const Provider = feature.isEnabled ? DefaultProvider : FallbackProvider;
export const useRollbar = feature.isEnabled ? useRollbarDefault : useRollbarFallback;
export const clientConfig: Configuration | undefined = feature.isEnabled ? {
accessToken: feature.clientToken,
environment: feature.environment,
payload: {
code_version: feature.codeVersion,
app_instance: feature.instance,
},
} : undefined;
import * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
import appConfig from 'configs/app';
import { RESOURCE_LOAD_ERROR_MESSAGE } from 'lib/errors/throwOnResourceLoadError';
const feature = appConfig.features.sentry;
export const config: Sentry.BrowserOptions | undefined = (() => {
if (!feature.isEnabled) {
return;
}
const tracesSampleRate: number | undefined = (() => {
switch (feature.environment) {
case 'development':
return 1;
case 'staging':
return 0.75;
case 'production':
return 0.2;
}
})();
return {
environment: feature.environment,
dsn: feature.dsn,
release: feature.release,
enableTracing: feature.enableTracing,
tracesSampleRate,
integrations: feature.enableTracing ? [ new BrowserTracing() ] : undefined,
// error filtering settings
// were taken from here - https://docs.sentry.io/platforms/node/guides/azure-functions/configuration/filtering/#decluttering-sentry
ignoreErrors: [
// Random plugins/extensions
'top.GLOBALS',
// See: http://blog.errorception.com/2012/03/tale-of-unfindable-js-error.html
'originalCreateNotification',
'canvas.contentDocument',
'MyApp_RemoveAllHighlights',
'http://tt.epicplay.com',
'Can\'t find variable: ZiteReader',
'jigsaw is not defined',
'ComboSearch is not defined',
'http://loading.retry.widdit.com/',
'atomicFindClose',
// Facebook borked
'fb_xd_fragment',
// ISP "optimizing" proxy - `Cache-Control: no-transform` seems to reduce this. (thanks @acdha)
// See http://stackoverflow.com/questions/4113268/how-to-stop-javascript-injection-from-vodafone-proxy
'bmi_SafeAddOnload',
'EBCallBackMessageReceived',
// See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
'conduitPage',
// Generic error code from errors outside the security sandbox
'Script error.',
// Relay and WalletConnect errors
'The quota has been exceeded',
'Attempt to connect to relay via',
'WebSocket connection failed for URL: wss://relay.walletconnect.com',
// API errors
RESOURCE_LOAD_ERROR_MESSAGE,
],
denyUrls: [
// Facebook flakiness
/graph\.facebook\.com/i,
// Facebook blocked
/connect\.facebook\.net\/en_US\/all\.js/i,
// Woopra flakiness
/eatdifferent\.com\.woopra-ns\.com/i,
/static\.woopra\.com\/js\/woopra\.js/i,
// Chrome and other extensions
/extensions\//i,
/^chrome:\/\//i,
/^chrome-extension:\/\//i,
/^moz-extension:\/\//i,
// Other plugins
/127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb
/webappstoolbarba\.texthelp\.com\//i,
/metrics\.itunes\.apple\.com\.edgesuite\.net\//i,
// AD fetch failed errors
/czilladx\.com/i,
/coinzilla\.com/i,
/coinzilla\.io/i,
/slise\.xyz/i,
],
};
})();
export function configureScope(scope: Sentry.Scope) {
if (!feature.isEnabled) {
return;
}
scope.setTag('app_instance', feature.instance);
}
export function init() {
if (!config) {
return;
}
Sentry.init(config);
Sentry.configureScope(configureScope);
}
...@@ -18,13 +18,7 @@ const moduleExports = { ...@@ -18,13 +18,7 @@ const moduleExports = {
'swagger-ui-react', 'swagger-ui-react',
], ],
reactStrictMode: true, reactStrictMode: true,
webpack(config, { webpack }) { webpack(config) {
config.plugins.push(
new webpack.DefinePlugin({
__SENTRY_DEBUG__: false,
__SENTRY_TRACING__: false,
}),
);
config.module.rules.push( config.module.rules.push(
{ {
test: /\.svg$/, test: /\.svg$/,
......
...@@ -9,7 +9,6 @@ import useAdblockDetect from 'lib/hooks/useAdblockDetect'; ...@@ -9,7 +9,6 @@ import useAdblockDetect from 'lib/hooks/useAdblockDetect';
import useGetCsrfToken from 'lib/hooks/useGetCsrfToken'; import useGetCsrfToken from 'lib/hooks/useGetCsrfToken';
import * as metadata from 'lib/metadata'; import * as metadata from 'lib/metadata';
import * as mixpanel from 'lib/mixpanel'; import * as mixpanel from 'lib/mixpanel';
import { init as initSentry } from 'lib/sentry/config';
interface Props<Pathname extends Route['pathname']> { interface Props<Pathname extends Route['pathname']> {
pathname: Pathname; pathname: Pathname;
...@@ -18,8 +17,6 @@ interface Props<Pathname extends Route['pathname']> { ...@@ -18,8 +17,6 @@ interface Props<Pathname extends Route['pathname']> {
apiData?: PageProps<Pathname>['apiData']; apiData?: PageProps<Pathname>['apiData'];
} }
initSentry();
const PageNextJs = <Pathname extends Route['pathname']>(props: Props<Pathname>) => { const PageNextJs = <Pathname extends Route['pathname']>(props: Props<Pathname>) => {
const { title, description, opengraph, canonical } = metadata.generate(props, props.apiData); const { title, description, opengraph, canonical } = metadata.generate(props, props.apiData);
......
...@@ -15,8 +15,8 @@ function generateCspPolicy() { ...@@ -15,8 +15,8 @@ function generateCspPolicy() {
descriptors.marketplace(), descriptors.marketplace(),
descriptors.mixpanel(), descriptors.mixpanel(),
descriptors.monaco(), descriptors.monaco(),
descriptors.rollbar(),
descriptors.safe(), descriptors.safe(),
descriptors.sentry(),
descriptors.usernameApi(), descriptors.usernameApi(),
descriptors.walletConnect(), descriptors.walletConnect(),
); );
......
...@@ -11,25 +11,6 @@ const MAIN_DOMAINS = [ ...@@ -11,25 +11,6 @@ const MAIN_DOMAINS = [
config.app.host, config.app.host,
].filter(Boolean); ].filter(Boolean);
const getCspReportUrl = () => {
try {
const sentryFeature = config.features.sentry;
if (!sentryFeature.isEnabled || !process.env.SENTRY_CSP_REPORT_URI) {
return;
}
const url = new URL(process.env.SENTRY_CSP_REPORT_URI);
// https://docs.sentry.io/product/security-policy-reporting/#additional-configuration
url.searchParams.set('sentry_environment', sentryFeature.environment);
sentryFeature.release && url.searchParams.set('sentry_release', sentryFeature.release);
return url.toString();
} catch (error) {
return;
}
};
const externalFontsDomains = (() => { const externalFontsDomains = (() => {
try { try {
return [ return [
...@@ -152,17 +133,5 @@ export function app(): CspDev.DirectiveDescriptor { ...@@ -152,17 +133,5 @@ export function app(): CspDev.DirectiveDescriptor {
// allow remix.ethereum.org to embed our contract page in iframe // allow remix.ethereum.org to embed our contract page in iframe
'remix.ethereum.org', 'remix.ethereum.org',
], ],
...((() => {
if (!config.features.sentry.isEnabled) {
return {};
}
return {
'report-uri': [
getCspReportUrl(),
].filter(Boolean),
};
})()),
}; };
} }
...@@ -10,7 +10,7 @@ export { helia } from './helia'; ...@@ -10,7 +10,7 @@ export { helia } from './helia';
export { marketplace } from './marketplace'; export { marketplace } from './marketplace';
export { mixpanel } from './mixpanel'; export { mixpanel } from './mixpanel';
export { monaco } from './monaco'; export { monaco } from './monaco';
export { rollbar } from './rollbar';
export { safe } from './safe'; export { safe } from './safe';
export { sentry } from './sentry';
export { usernameApi } from './usernameApi'; export { usernameApi } from './usernameApi';
export { walletConnect } from './walletConnect'; export { walletConnect } from './walletConnect';
import type CspDev from 'csp-dev'; import type CspDev from 'csp-dev';
export function sentry(): CspDev.DirectiveDescriptor { import config from 'configs/app';
export function rollbar(): CspDev.DirectiveDescriptor {
if (!config.features.rollbar.isEnabled) {
return {};
}
return { return {
'connect-src': [ 'connect-src': [
'sentry.io', 'api.rollbar.com',
'*.sentry.io',
], ],
}; };
} }
import * as Sentry from '@sentry/react';
import React from 'react'; import React from 'react';
import type { NextPageWithLayout } from 'nextjs/types'; import type { NextPageWithLayout } from 'nextjs/types';
import PageNextJs from 'nextjs/PageNextJs'; import PageNextJs from 'nextjs/PageNextJs';
import { useRollbar } from 'lib/rollbar';
import AppError from 'ui/shared/AppError/AppError'; import AppError from 'ui/shared/AppError/AppError';
import LayoutError from 'ui/shared/layout/LayoutError'; import LayoutError from 'ui/shared/layout/LayoutError';
const error = new Error('Not found', { cause: { status: 404 } }); const error = new Error('Not found', { cause: { status: 404 } });
const Page: NextPageWithLayout = () => { const Page: NextPageWithLayout = () => {
const rollbar = useRollbar();
React.useEffect(() => { React.useEffect(() => {
Sentry.captureException(new Error('Page not found'), { tags: { source: '404' } }); rollbar?.error('Page not found');
}, []); }, [ rollbar ]);
return ( return (
<PageNextJs pathname="/404"> <PageNextJs pathname="/404">
......
import { type ChakraProps } from '@chakra-ui/react'; import { type ChakraProps } from '@chakra-ui/react';
import { GrowthBookProvider } from '@growthbook/growthbook-react'; import { GrowthBookProvider } from '@growthbook/growthbook-react';
import * as Sentry from '@sentry/react';
import { QueryClientProvider } from '@tanstack/react-query'; import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
...@@ -19,6 +18,7 @@ import { SettingsContextProvider } from 'lib/contexts/settings'; ...@@ -19,6 +18,7 @@ import { SettingsContextProvider } from 'lib/contexts/settings';
import { growthBook } from 'lib/growthbook/init'; import { growthBook } from 'lib/growthbook/init';
import useLoadFeatures from 'lib/growthbook/useLoadFeatures'; import useLoadFeatures from 'lib/growthbook/useLoadFeatures';
import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation'; import useNotifyOnNavigation from 'lib/hooks/useNotifyOnNavigation';
import { clientConfig as rollbarConfig, Provider as RollbarProvider } from 'lib/rollbar';
import { SocketProvider } from 'lib/socket/context'; import { SocketProvider } from 'lib/socket/context';
import RewardsLoginModal from 'ui/rewards/login/RewardsLoginModal'; import RewardsLoginModal from 'ui/rewards/login/RewardsLoginModal';
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary'; import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
...@@ -53,17 +53,13 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { ...@@ -53,17 +53,13 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const queryClient = useQueryClientConfig(); const queryClient = useQueryClientConfig();
const handleError = React.useCallback((error: Error) => {
Sentry.captureException(error);
}, []);
const getLayout = Component.getLayout ?? ((page) => <Layout>{ page }</Layout>); const getLayout = Component.getLayout ?? ((page) => <Layout>{ page }</Layout>);
return ( return (
<ChakraProvider cookies={ pageProps.cookies }> <ChakraProvider cookies={ pageProps.cookies }>
<RollbarProvider config={ rollbarConfig }>
<AppErrorBoundary <AppErrorBoundary
{ ...ERROR_SCREEN_STYLES } { ...ERROR_SCREEN_STYLES }
onError={ handleError }
Container={ AppErrorGlobalContainer } Container={ AppErrorGlobalContainer }
> >
<Web3ModalProvider> <Web3ModalProvider>
...@@ -89,6 +85,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { ...@@ -89,6 +85,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
</AppContextProvider> </AppContextProvider>
</Web3ModalProvider> </Web3ModalProvider>
</AppErrorBoundary> </AppErrorBoundary>
</RollbarProvider>
</ChakraProvider> </ChakraProvider>
); );
} }
......
...@@ -14,7 +14,6 @@ import { ...@@ -14,7 +14,6 @@ import {
IconButton, IconButton,
Skeleton, Skeleton,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import * as Sentry from '@sentry/react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import QRCode from 'qrcode'; import QRCode from 'qrcode';
import React from 'react'; import React from 'react';
...@@ -23,6 +22,7 @@ import type { Address as AddressType } from 'types/api/address'; ...@@ -23,6 +22,7 @@ import type { Address as AddressType } from 'types/api/address';
import getPageType from 'lib/mixpanel/getPageType'; import getPageType from 'lib/mixpanel/getPageType';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import { useRollbar } from 'lib/rollbar';
import AddressEntity from 'ui/shared/entities/address/AddressEntity'; import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
...@@ -40,6 +40,7 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => { ...@@ -40,6 +40,7 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const router = useRouter(); const router = useRouter();
const rollbar = useRollbar();
const [ qr, setQr ] = React.useState(''); const [ qr, setQr ] = React.useState('');
const [ error, setError ] = React.useState(''); const [ error, setError ] = React.useState('');
...@@ -51,7 +52,7 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => { ...@@ -51,7 +52,7 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => {
QRCode.toString(address.hash, SVG_OPTIONS, (error: Error | null | undefined, svg: string) => { QRCode.toString(address.hash, SVG_OPTIONS, (error: Error | null | undefined, svg: string) => {
if (error) { if (error) {
setError('We were unable to generate QR code.'); setError('We were unable to generate QR code.');
Sentry.captureException(error, { tags: { source: 'qr_code' } }); rollbar?.warn('QR code generation failed');
return; return;
} }
...@@ -60,7 +61,7 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => { ...@@ -60,7 +61,7 @@ const AddressQrCode = ({ address, className, isLoading }: Props) => {
mixpanel.logEvent(mixpanel.EventTypes.QR_CODE, { 'Page type': pageType }); mixpanel.logEvent(mixpanel.EventTypes.QR_CODE, { 'Page type': pageType });
}); });
} }
}, [ address.hash, isOpen, onClose, pageType ]); }, [ address.hash, isOpen, onClose, pageType, rollbar ]);
if (isLoading) { if (isLoading) {
return <Skeleton className={ className } w="36px" h="32px" borderRadius="base"/>; return <Skeleton className={ className } w="36px" h="32px" borderRadius="base"/>;
......
import { VStack, Textarea, Button, Alert, AlertTitle, AlertDescription, Code, Flex, Box } from '@chakra-ui/react'; import { VStack, Textarea, Button, Alert, AlertTitle, AlertDescription, Code, Flex, Box } from '@chakra-ui/react';
import * as Sentry from '@sentry/react';
import mixpanel from 'mixpanel-browser'; import mixpanel from 'mixpanel-browser';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
...@@ -9,12 +8,13 @@ import * as cookies from 'lib/cookies'; ...@@ -9,12 +8,13 @@ import * as cookies from 'lib/cookies';
import useFeatureValue from 'lib/growthbook/useFeatureValue'; import useFeatureValue from 'lib/growthbook/useFeatureValue';
import useGradualIncrement from 'lib/hooks/useGradualIncrement'; import useGradualIncrement from 'lib/hooks/useGradualIncrement';
import useToast from 'lib/hooks/useToast'; import useToast from 'lib/hooks/useToast';
import { useRollbar } from 'lib/rollbar';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
const Login = () => { const Login = () => {
const rollbar = useRollbar();
const toast = useToast(); const toast = useToast();
const [ num, setNum ] = useGradualIncrement(0); const [ num, setNum ] = useGradualIncrement(0);
const testFeature = useFeatureValue('test_value', 'fallback'); const testFeature = useFeatureValue('test_value', 'fallback');
const [ isFormVisible, setFormVisibility ] = React.useState(false); const [ isFormVisible, setFormVisibility ] = React.useState(false);
...@@ -23,12 +23,12 @@ const Login = () => { ...@@ -23,12 +23,12 @@ const Login = () => {
React.useEffect(() => { React.useEffect(() => {
const token = cookies.get(cookies.NAMES.API_TOKEN); const token = cookies.get(cookies.NAMES.API_TOKEN);
setFormVisibility(Boolean(!token && config.features.account.isEnabled)); setFormVisibility(Boolean(!token && config.features.account.isEnabled));
// throw new Error('Test error'); // throw new Error('Render error');
}, []); }, []);
const checkSentry = React.useCallback(() => { const checkRollbar = React.useCallback(() => {
Sentry.captureException(new Error('Test error'), { tags: { source: 'test' } }); rollbar?.error('Test error', { payload: 'foo' });
}, []); }, [ rollbar ]);
const checkMixpanel = React.useCallback(() => { const checkMixpanel = React.useCallback(() => {
mixpanel.track('Test event', { my_prop: 'foo bar' }); mixpanel.track('Test event', { my_prop: 'foo bar' });
...@@ -80,7 +80,7 @@ const Login = () => { ...@@ -80,7 +80,7 @@ const Login = () => {
</> </>
) } ) }
<Flex columnGap={ 2 }> <Flex columnGap={ 2 }>
<Button colorScheme="red" onClick={ checkSentry }>Check Sentry</Button> <Button colorScheme="red" onClick={ checkRollbar }>Check Rollbar</Button>
<Button colorScheme="teal" onClick={ checkMixpanel }>Check Mixpanel</Button> <Button colorScheme="teal" onClick={ checkMixpanel }>Check Mixpanel</Button>
</Flex> </Flex>
<Flex columnGap={ 2 } alignItems="center"> <Flex columnGap={ 2 } alignItems="center">
......
import { chakra } from '@chakra-ui/react'; import { chakra } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import getErrorCauseStatusCode from 'lib/errors/getErrorCauseStatusCode';
import { useRollbar } from 'lib/rollbar';
import ErrorBoundary from 'ui/shared/ErrorBoundary'; import ErrorBoundary from 'ui/shared/ErrorBoundary';
import AppError from './AppError'; import AppError from './AppError';
...@@ -8,11 +10,12 @@ import AppError from './AppError'; ...@@ -8,11 +10,12 @@ import AppError from './AppError';
interface Props { interface Props {
className?: string; className?: string;
children: React.ReactNode; children: React.ReactNode;
onError?: (error: Error) => void;
Container?: React.FC<{ children: React.ReactNode }>; Container?: React.FC<{ children: React.ReactNode }>;
} }
const AppErrorBoundary = ({ className, children, onError, Container }: Props) => { const AppErrorBoundary = ({ className, children, Container }: Props) => {
const rollbar = useRollbar();
const renderErrorScreen = React.useCallback((error?: Error) => { const renderErrorScreen = React.useCallback((error?: Error) => {
const content = <AppError error={ error } className={ className }/>; const content = <AppError error={ error } className={ className }/>;
...@@ -22,8 +25,21 @@ const AppErrorBoundary = ({ className, children, onError, Container }: Props) => ...@@ -22,8 +25,21 @@ const AppErrorBoundary = ({ className, children, onError, Container }: Props) =>
return content; return content;
}, [ className, Container ]); }, [ className, Container ]);
const handleError = React.useCallback((error: Error) => {
const statusCode = getErrorCauseStatusCode(error);
if (statusCode || !rollbar) {
// For now, we are not interested in logging errors from the API.
// If an error from a resource should be logged, please consider passing "logError: true" to the useApiQuery or useApiFetch hook.
return;
}
// To this point, there can only be errors that lead to a page crash.
// Therefore, we set the error level to "critical."
rollbar.critical(error);
}, [ rollbar ]);
return ( return (
<ErrorBoundary renderErrorScreen={ renderErrorScreen } onError={ onError }> <ErrorBoundary renderErrorScreen={ renderErrorScreen } onError={ handleError }>
{ children } { children }
</ErrorBoundary> </ErrorBoundary>
); );
......
import * as Sentry from '@sentry/react';
import React from 'react'; import React from 'react';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import { useRollbar } from 'lib/rollbar';
import useProfileQuery from 'ui/snippets/auth/useProfileQuery'; import useProfileQuery from 'ui/snippets/auth/useProfileQuery';
export default function useRedirectForInvalidAuthToken() { export default function useRedirectForInvalidAuthToken() {
const rollbar = useRollbar();
const profileQuery = useProfileQuery(); const profileQuery = useProfileQuery();
const errorStatus = profileQuery.error?.status; const errorStatus = profileQuery.error?.status;
...@@ -13,10 +14,9 @@ export default function useRedirectForInvalidAuthToken() { ...@@ -13,10 +14,9 @@ export default function useRedirectForInvalidAuthToken() {
const apiToken = cookies.get(cookies.NAMES.API_TOKEN); const apiToken = cookies.get(cookies.NAMES.API_TOKEN);
if (apiToken) { if (apiToken) {
Sentry.captureException(new Error('Invalid API token'), { tags: { source: 'invalid_api_token' } });
cookies.remove(cookies.NAMES.API_TOKEN); cookies.remove(cookies.NAMES.API_TOKEN);
window.location.assign('/'); window.location.assign('/');
} }
} }
}, [ errorStatus ]); }, [ errorStatus, rollbar ]);
} }
This diff is collapsed.
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