-
Couldn't load subscription status.
- Fork 29.7k
Improve @next/plugin-sentry support and expose additional to plugin hooks #18272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| /** | ||
| * TODO(kamil): Unify SDK configuration options. | ||
| * Workaround for callbacks, integrations and any unserializable config options. | ||
| **/ | ||
| exports.serverConfig = {} | ||
| exports.clientConfig = {} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| export const getDsn = () => | ||
| process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN | ||
|
|
||
| export const getRelease = () => | ||
| process.env.SENTRY_RELEASE || | ||
| process.env.NEXT_PUBLIC_SENTRY_RELEASE || | ||
| process.env.VERCEL_GITHUB_COMMIT_SHA || | ||
| process.env.VERCEL_GITLAB_COMMIT_SHA || | ||
| process.env.VERCEL_BITBUCKET_COMMIT_SHA |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| const { serverConfig, clientConfig } = require('./config.js') | ||
|
|
||
| const Sentry = require('@sentry/minimal') | ||
| // NOTE(kamiL): @sentry/minimal doesn't expose this method, as it's not env-agnostic, but we still want to test it here | ||
| Sentry.showReportDialog = (...args) => { | ||
| Sentry._callOnClient('showReportDialog', ...args) | ||
| } | ||
|
|
||
| exports.Sentry = Sentry | ||
| exports.serverConfig = serverConfig | ||
| exports.clientConfig = clientConfig |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,16 +7,15 @@ | |
| }, | ||
| "nextjs": { | ||
| "name": "Sentry", | ||
| "required-env": [ | ||
| "SENTRY_DSN", | ||
| "SENTRY_RELEASE" | ||
| ] | ||
| "required-env": [] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are there no longer any required envs? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See the PR description. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should fix the bug with the key please instead of bypassing it! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I totally agree. This PR is the effect of PoC work, and it was necessary to go around this limitation for demo purposes. |
||
| }, | ||
| "peerDependencies": { | ||
| "next": "*" | ||
| }, | ||
| "dependencies": { | ||
| "@sentry/browser": "5.7.1", | ||
| "@sentry/node": "5.7.1" | ||
| "@sentry/integrations": "5.27.0", | ||
| "@sentry/node": "5.27.0", | ||
| "@sentry/browser": "5.27.0", | ||
| "@sentry/minimal": "5.27.0" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,98 @@ | ||
| # Unstable @next/plugin-sentry | ||
| # Next.js + Sentry | ||
|
|
||
| This package is still very experimental and should not be used at this point | ||
| Capture unhandled exceptions with Sentry in your Next.js project. | ||
|
|
||
| ## Installation | ||
|
|
||
| ``` | ||
| npm install @next/plugin-sentry | ||
| ``` | ||
|
|
||
| or | ||
|
|
||
| ``` | ||
| yarn add @next/plugin-sentry | ||
| ``` | ||
|
|
||
| Provide necessary env variables through `.env` or available way | ||
|
|
||
| ``` | ||
| NEXT_PUBLIC_SENTRY_DSN | ||
| // variables below are required only when using with @next/sentry-source-maps | ||
| NEXT_PUBLIC_SENTRY_RELEASE | ||
| SENTRY_PROJECT | ||
| SENTRY_ORG | ||
| SENTRY_AUTH_TOKEN | ||
| ``` | ||
|
|
||
| ### Usage | ||
|
|
||
| Create a next.config.js | ||
|
|
||
| ```js | ||
| // next.config.js | ||
| module.exports = { | ||
| experimental: { plugins: true }, | ||
| } | ||
| ``` | ||
|
|
||
| With only that, you'll get a complete error coverage for your application. | ||
| If you want to use Sentry SDK APIs, you can do so in both, server-side and client-side code with the same namespace from the plugin. | ||
|
|
||
| ```js | ||
| import { Sentry } from '@next/plugin-sentry' | ||
|
|
||
| const MyComponent = () => <h1>Server Test 1</h1> | ||
|
|
||
| export function getServerSideProps() { | ||
| if (!this.veryImportantValue) { | ||
| Sentry.withScope((scope) => { | ||
| scope.setTag('method', 'getServerSideProps') | ||
| Sentry.captureMessage('veryImportantValue is missing') | ||
| }) | ||
| } | ||
|
|
||
| return {} | ||
| } | ||
|
|
||
| export default MyComponent | ||
| ``` | ||
|
|
||
| ### Configuration | ||
|
|
||
| There are two ways to configure Sentry SDK. One through `next.config.js` which allows for the full configuration of the server-side code, and partial configuration of client-side code. And additional method for client-side code. | ||
|
|
||
| ```js | ||
| // next.config.js | ||
| module.exports = { | ||
| experimental: { plugins: true }, | ||
| // Sentry.init config for server-side code. Can accept any available config option. | ||
| serverRuntimeConfig: { | ||
| sentry: { | ||
| type: 'server', | ||
| }, | ||
| }, | ||
| // Sentry.init config for client-side code (and fallback for server-side) | ||
| // can accept only serializeable values. For more granular control see below. | ||
| publicRuntimeConfig: { | ||
| sentry: { | ||
| type: 'client', | ||
| }, | ||
| }, | ||
|
Comment on lines
+71
to
+82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be moved to your own |
||
| } | ||
| ``` | ||
|
|
||
| If you need to pass config options for the client-side, that are non-serializable, for example `beforeSend` or `beforeBreadcrumb`: | ||
|
|
||
| ```js | ||
| // _app.js | ||
| import { clientConfig } from '@next/plugin-sentry' | ||
|
|
||
| clientConfig.beforeSend = () => { | ||
| /* ... */ | ||
| } | ||
| clientConfig.beforeBreadcrumb = () => { | ||
| /* ... */ | ||
| } | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,12 @@ | ||
| import * as Sentry from '@sentry/browser' | ||
| import { withScope, captureException } from '@sentry/browser' | ||
|
|
||
| export default async function onErrorClient({ err }) { | ||
| Sentry.captureException(err) | ||
| export default async function onErrorClient({ err, errorInfo }) { | ||
| withScope((scope) => { | ||
| if (typeof errorInfo?.componentStack === 'string') { | ||
| scope.setContext('react', { | ||
| componentStack: errorInfo.componentStack.trim(), | ||
| }) | ||
| } | ||
| captureException(err) | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,27 @@ | ||
| import * as Sentry from '@sentry/node' | ||
| import { captureException, flush, Handlers, withScope } from '@sentry/node' | ||
| import getConfig from 'next/config' | ||
|
|
||
| export default async function onErrorServer({ err }) { | ||
| Sentry.captureException(err) | ||
| const { parseRequest } = Handlers | ||
|
|
||
| export default async function onErrorServer({ err, req }) { | ||
| const { serverRuntimeConfig = {}, publicRuntimeConfig = {} } = | ||
| getConfig() || {} | ||
|
Comment on lines
+7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| const sentryTimeout = | ||
| serverRuntimeConfig.sentryTimeout || | ||
| publicRuntimeConfig.sentryTimeout || | ||
| 2000 | ||
|
|
||
| withScope((scope) => { | ||
| if (req) { | ||
| scope.addEventProcessor((event) => | ||
| parseRequest(event, req, { | ||
| // TODO(kamil): 'cookies' and 'query_string' use `dynamicRequire` which has a bug in SSR envs right now. | ||
| request: ['data', 'headers', 'method', 'url'], | ||
| }) | ||
| ) | ||
| } | ||
| captureException(err) | ||
| }) | ||
|
|
||
| await flush(sentryTimeout) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,21 @@ | ||
| import * as Sentry from '@sentry/browser' | ||
| import { init } from '@sentry/browser' | ||
| import getConfig from 'next/config' | ||
|
|
||
| import { getDsn, getRelease } from '../env' | ||
| import { clientConfig } from '../config' | ||
|
|
||
| export default async function initClient() { | ||
| // by default `@sentry/browser` is configured with defaultIntegrations | ||
| // which capture uncaughtExceptions and unhandledRejections | ||
| Sentry.init({ | ||
| dsn: process.env.SENTRY_DSN, | ||
| release: process.env.SENTRY_RELEASE, | ||
| /** | ||
| * TODO(kamil): Unify SDK configuration options. | ||
| * RuntimeConfig cannot be used for callbacks and integrations as it supports only serializable data. | ||
| **/ | ||
| const { publicRuntimeConfig = {} } = getConfig() || {} | ||
| const runtimeConfig = publicRuntimeConfig.sentry || {} | ||
|
Comment on lines
+12
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Timer What do you suggest to replace it? |
||
|
|
||
| init({ | ||
| dsn: getDsn(), | ||
| ...(getRelease() && { release: getRelease() }), | ||
| ...runtimeConfig, | ||
| ...clientConfig, | ||
| }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,39 @@ | ||
| import * as Sentry from '@sentry/node' | ||
| import { init } from '@sentry/node' | ||
| import { RewriteFrames } from '@sentry/integrations' | ||
| import getConfig from 'next/config' | ||
|
|
||
| import { getDsn, getRelease } from '../env' | ||
| import { serverConfig } from '../config' | ||
|
|
||
| export default async function initServer() { | ||
| // by default `@sentry/node` is configured with defaultIntegrations | ||
| // which capture uncaughtExceptions and unhandledRejections | ||
| // see here for more https://github.com/getsentry/sentry-javascript/blob/46a02209bafcbc1603c769476ba0a1eaa450759d/packages/node/src/sdk.ts#L22 | ||
| Sentry.init({ | ||
| dsn: process.env.SENTRY_DSN, | ||
| release: process.env.SENTRY_RELEASE, | ||
| /** | ||
| * TODO(kamil): Unify SDK configuration options. | ||
| * RuntimeConfig cannot be used for callbacks and integrations as it supports only serializable data. | ||
| **/ | ||
| const { serverRuntimeConfig = {}, publicRuntimeConfig = {} } = | ||
| getConfig() || {} | ||
| const runtimeConfig = | ||
| serverRuntimeConfig.sentry || publicRuntimeConfig.sentry || {} | ||
|
Comment on lines
+13
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| init({ | ||
| dsn: getDsn(), | ||
| ...(getRelease() && { release: getRelease() }), | ||
| ...runtimeConfig, | ||
| ...serverConfig, | ||
| integrations: [ | ||
| new RewriteFrames({ | ||
| iteratee: (frame) => { | ||
| try { | ||
| const [, path] = frame.filename.split('.next/') | ||
| if (path) { | ||
| frame.filename = `app:///_next/${path}` | ||
| } | ||
| } catch {} | ||
| return frame | ||
| }, | ||
| }), | ||
| ...(runtimeConfig.integrations || []), | ||
| ...(serverConfig.integrations || []), | ||
| ], | ||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The runtime configuration approach is not needed if you compile the configuration values into the bundle (prefixing using
NEXT_PUBLIC_. However keep in mind that we shouldn't do that for thegetReleasepart given it will cause every single build to invalidate the webpack cache.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but there's no way to provide additional configuration to
Sentry.initwithout that approach, asNEXT_PUBLIC_supports only serializable values.