未验证 提交 a1b46817 编写于 作者: L Luis Alvarez D 提交者: GitHub

[Examples] Update with-sentry-simple (#13074)

Moved from `getInitialProps` to the new data fetching methods. Updated the readme and simplified the implementation for pages.
上级 b3e52336
...@@ -2,6 +2,12 @@ ...@@ -2,6 +2,12 @@
# Sentry (Simple Example) # Sentry (Simple Example)
This is a simple example showing how to use [Sentry](https://sentry.io) to catch & report errors on both client + server side.
- `_app.js` renders on both the server and client. It initializes Sentry to catch any unhandled exceptions
- `_error.js` is rendered by Next.js while handling certain types of exceptions for you. It is overridden so those exceptions can be passed along to Sentry
- `next.config.js` enables source maps in production for Sentry and swaps out `@sentry/node` for `@sentry/browser` when building the client bundle
## How To Use ## How To Use
### Using `create-next-app` ### Using `create-next-app`
...@@ -25,58 +31,42 @@ cd with-sentry-simple ...@@ -25,58 +31,42 @@ cd with-sentry-simple
Install it and run: Install it and run:
**NPM**
```bash ```bash
npm install npm install
npm run dev npm run dev
``` # or
**Yarn**
```bash
yarn yarn
yarn dev yarn dev
``` ```
Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
## About Example ## Notes
This is a simple example showing how to use [Sentry](https://sentry.io) to catch & report errors on both client + server side.
- `_app.js` renders on both the server and client. It initializes Sentry to catch any unhandled exceptions
- `_error.js` is rendered by Next.js while handling certain types of exceptions for you. It is overridden so those exceptions can be passed along to Sentry
- `next.config.js` enables source maps in production for Sentry and swaps out `@sentry/node` for `@sentry/browser` when building the client bundle
**Note**: By default, neither sourcemaps nor error tracking is enabled in development mode (see Configuration).
**Note**: When enabled in development mode, error handling [works differently than in production](https://nextjs.org/docs#custom-error-handling) as `_error.js` is never actually called.
**Note**: The build output will contain warning about unhandled Promise rejections. This caused by the test pages, and is expected.
**Note**: The version of `@zeit/next-source-maps` (`0.0.4-canary.1`) is important and must be specified since it is not yet the default. Otherwise [source maps will not be generated for the server](https://github.com/zeit/next-plugins/issues/377).
**Note**: Both `@zeit/next-source-maps` and `@sentry/webpack-plugin` are added to dependencies (rather than `devDependencies`) is because if used with SSR (ex. heroku), these plugins are used during production for generating the source-maps and sending them to sentry. - By default, neither sourcemaps nor error tracking is enabled in development mode (see Configuration).
- When enabled in development mode, error handling [works differently than in production](https://nextjs.org/docs#custom-error-handling) as `_error.js` is never actually called.
- The build output will contain warning about unhandled Promise rejections. This caused by the test pages, and is expected.
- The version of `@zeit/next-source-maps` (`0.0.4-canary.1`) is important and must be specified since it is not yet the default. Otherwise [source maps will not be generated for the server](https://github.com/zeit/next-plugins/issues/377).
- Both `@zeit/next-source-maps` and `@sentry/webpack-plugin` are added to dependencies (rather than `devDependencies`) is because if used with SSR, these plugins are used during production for generating the source-maps and sending them to sentry.
### Configuration ## Configuration
#### Error tracking ### Error tracking
1. Copy your Sentry DSN. You can get it from the settings of your project in **Client Keys (DSN)**. Then, copy the string labeled **DSN (Public)**. 1. Copy your Sentry DSN. You can get it from the settings of your project in **Client Keys (DSN)**. Then, copy the string labeled **DSN (Public)**.
2. Put the DSN inside the `SENTRY_DSN` environment variable. 2. Put the DSN inside the `SENTRY_DSN` environment variable inside a new environment file called `.env.local`
**Note:** Error tracking is disabled in development mode using the `NODE_ENV` environment variable. To change this behaviour, remove the `enabled` property from the `Sentry.init()` call inside your `_app.js` file. More details about how `NODE_ENV` is set in next deployments can be found [here](https://nextjs.org/docs#production-deployment). > **Note:** Error tracking is disabled in development mode using the `NODE_ENV` environment variable. To change this behaviour, remove the `enabled` property from the `Sentry.init()` call inside your `_app.js` file.
#### Automatic sourcemap upload (optional) ### Automatic sourcemap upload (optional)
1. Set up the `SENTRY_DSN` environment variable as described above. 1. Set up the `SENTRY_DSN` environment variable as described above.
2. Save your Sentry Organization slug inside the `SENTRY_ORG` and your project slug inside the `SENTRY_PROJECT` environment variables. 2. Save your Sentry Organization slug inside the `SENTRY_ORG` and your project slug inside the `SENTRY_PROJECT` environment variables.
3. Create an auth token in Sentry. The recommended way to do this is by creating a new internal integration for your organization. To do so, go into Settings > Developer Settings > New internal integration. After the integration is created, copy the Token. 3. Create an auth token in Sentry. The recommended way to do this is by creating a new internal integration for your organization. To do so, go into Settings > Developer Settings > New internal integration. After the integration is created, copy the Token.
4. Save the token inside the `SENTRY_AUTH_TOKEN` environment variable. 4. Save the token inside the `SENTRY_AUTH_TOKEN` environment variable.
**Note:** Sourcemap upload is disabled in development mode using the `NODE_ENV` environment variable. To change this behaviour, remove the `NODE_ENV === 'production'` check from your `next.config.js` file. More details about how `NODE_ENV` is set in next deployments can be found [here](https://nextjs.org/docs#production-deployment). > **Note:** Sourcemap upload is disabled in development mode using the `NODE_ENV` environment variable. To change this behaviour, remove the `NODE_ENV === 'production'` check from your `next.config.js` file.
#### Other configuration options ### Other configuration options
More configurations is available for [Sentry webpack plugin](https://github.com/getsentry/sentry-webpack-plugin) and using [Sentry Configuration variables](https://docs.sentry.io/cli/configuration/) for defining the releases/verbosity/etc. More configurations are available for the [Sentry webpack plugin](https://github.com/getsentry/sentry-webpack-plugin) using [Sentry Configuration variables](https://docs.sentry.io/cli/configuration/) for defining the releases/verbosity/etc.
...@@ -5,17 +5,16 @@ const withSourceMaps = require('@zeit/next-source-maps')() ...@@ -5,17 +5,16 @@ const withSourceMaps = require('@zeit/next-source-maps')()
// Use the SentryWebpack plugin to upload the source maps during build step // Use the SentryWebpack plugin to upload the source maps during build step
const SentryWebpackPlugin = require('@sentry/webpack-plugin') const SentryWebpackPlugin = require('@sentry/webpack-plugin')
const { const {
SENTRY_DSN, NEXT_PUBLIC_SENTRY_DSN: SENTRY_DSN,
SENTRY_ORG, SENTRY_ORG,
SENTRY_PROJECT, SENTRY_PROJECT,
SENTRY_AUTH_TOKEN, SENTRY_AUTH_TOKEN,
NODE_ENV, NODE_ENV,
} = process.env } = process.env
process.env.SENTRY_DSN = SENTRY_DSN
module.exports = withSourceMaps({ module.exports = withSourceMaps({
env: {
SENTRY_DSN: process.env.SENTRY_DSN,
},
webpack: (config, options) => { webpack: (config, options) => {
// In `pages/_app.js`, Sentry is imported from @sentry/node. While // In `pages/_app.js`, Sentry is imported from @sentry/node. While
// @sentry/browser will run in a Node.js environment, @sentry/node will use // @sentry/browser will run in a Node.js environment, @sentry/node will use
......
import Error from 'next/error'
export default function NotFound() {
return <Error statusCode={404} />
}
import React from 'react'
import App from 'next/app'
import * as Sentry from '@sentry/node' import * as Sentry from '@sentry/node'
Sentry.init({ Sentry.init({
enabled: process.env.NODE_ENV === 'production', enabled: process.env.NODE_ENV === 'production',
dsn: process.env.SENTRY_DSN, dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
}) })
class MyApp extends App { export default function App({ Component, pageProps, err }) {
render() { // Workaround for https://github.com/zeit/next.js/issues/8592
const { Component, pageProps } = this.props return <Component {...pageProps} err={err} />
// Workaround for https://github.com/zeit/next.js/issues/8592
const { err } = this.props
const modifiedPageProps = { ...pageProps, err }
return <Component {...modifiedPageProps} />
}
} }
export default MyApp
import React from 'react'
import Error from 'next/error' import Error from 'next/error'
import * as Sentry from '@sentry/node' import * as Sentry from '@sentry/node'
...@@ -20,37 +19,26 @@ MyError.getInitialProps = async ({ res, err, asPath }) => { ...@@ -20,37 +19,26 @@ MyError.getInitialProps = async ({ res, err, asPath }) => {
// getInitialProps has run // getInitialProps has run
errorInitialProps.hasGetInitialPropsRun = true errorInitialProps.hasGetInitialPropsRun = true
if (res) { // Running on the server, the response object (`res`) is available.
// Running on the server, the response object is available. //
// // Next.js will pass an err on the server if a page's data fetching methods
// Next.js will pass an err on the server if a page's `getInitialProps` // threw or returned a Promise that rejected
// threw or returned a Promise that rejected //
// Running on the client (browser), Next.js will provide an err if:
if (res.statusCode === 404) { //
// Opinionated: do not record an exception in Sentry for 404 // - a page's `getInitialProps` threw or returned a Promise that rejected
return { statusCode: 404 } // - an exception was thrown somewhere in the React lifecycle (render,
} // componentDidMount, etc) that was caught by Next.js's React Error
// Boundary. Read more about what types of exceptions are caught by Error
if (err) { // Boundaries: https://reactjs.org/docs/error-boundaries.html
Sentry.captureException(err)
if (res?.statusCode === 404) {
return errorInitialProps // Opinionated: do not record an exception in Sentry for 404
} return { statusCode: 404 }
} else { }
// Running on the client (browser). if (err) {
// Sentry.captureException(err)
// Next.js will provide an err if: return errorInitialProps
//
// - a page's `getInitialProps` threw or returned a Promise that rejected
// - an exception was thrown somewhere in the React lifecycle (render,
// componentDidMount, etc) that was caught by Next.js's React Error
// Boundary. Read more about what types of exceptions are caught by Error
// Boundaries: https://reactjs.org/docs/error-boundaries.html
if (err) {
Sentry.captureException(err)
return errorInitialProps
}
} }
// If this point is reached, getInitialProps was called without any // If this point is reached, getInitialProps was called without any
......
import React from 'react' const doAsyncWork = () => Promise.reject(new Error('Client Test 1'))
doAsyncWork()
const Test1 = () => <h1>Client Test 1</h1> const Test1 = () => <h1>Client Test 1</h1>
Test1.getInitialProps = () => {
throw new Error('Client Test 1')
}
export default Test1 export default Test1
import React from 'react' // This code will run just fine on the server in Node.js, but process will be
// undefined in a browser. Note that `isProd = process.env.NODE_ENV` would have
// worked because Webpack's DefinePlugin will replace it with a string at build
// time
const env = process.env
const isProd = env.NODE_ENV === 'production'
const Test2 = () => <h1>Client Test 2</h1> const Test2 = () => (
<>
Test2.getInitialProps = () => Promise.reject(new Error('Client Test 2')) <h1>Client Test 2</h1>
<p>isProd: {isProd}</p>
</>
)
export default Test2 export default Test2
import React from 'react' import { useEffect } from 'react'
const Test3 = () => <h1>Client Test 3</h1> const Test3 = () => {
useEffect(() => {
throw new Error('Client Test 3')
}, [])
Test3.getInitialProps = () => { return <h1>Client Test 3</h1>
const doAsyncWork = () => Promise.reject(new Error('Client Test 3'))
doAsyncWork()
return {}
} }
export default Test3 export default Test3
import React from 'react' import { useEffect } from 'react'
const doAsyncWork = () => Promise.reject(new Error('Client Test 4')) const Test4 = () => {
doAsyncWork() useEffect(function () {
async function doTest() {
const doAsyncWork = () => Promise.reject(new Error('Client Test 4'))
await doAsyncWork()
}
doTest()
}, [])
const Test4 = () => <h1>Client Test 4</h1> return <h1>Client Test 4</h1>
}
export default Test4 export default Test4
import React from 'react'
// This code will run just fine on the server in Node.js, but process will be
// undefined in a browser. Note that `isProd = process.env.NODE_ENV` would have
// worked because Webpack's DefinePlugin will replace it with a string at build
// time: https://nextjs.org/docs#build-time-configuration
const env = process.env
const isProd = env.NODE_ENV === 'production'
const Test5 = () => ( const Test5 = () => (
<React.Fragment> <>
<h1>Client Test 5</h1> <h1>Client Test 5</h1>
<p>isProd: {isProd}</p> <button
</React.Fragment> onClick={() => {
throw new Error('Client Test 5')
}}
>
Click me to throw an Error
</button>
</>
) )
export default Test5 export default Test5
import React from 'react'
const Test6 = () => {
React.useEffect(() => {
throw new Error('Client Test 6')
}, [])
return <h1>Client Test 6</h1>
}
export default Test6
import React from 'react'
const Test7 = () => {
React.useEffect(function () {
async function doTest() {
const doAsyncWork = () => Promise.reject(new Error('Client Test 7'))
const result = await doAsyncWork()
console.log(result)
}
doTest()
}, [])
return <h1>Client Test 7</h1>
}
export default Test7
import React from 'react'
const Test8 = () => (
<React.Fragment>
<h1>Client Test 8</h1>
<button
onClick={() => {
throw new Error('Client Test 8')
}}
>
Click me to throw an Error
</button>
</React.Fragment>
)
export default Test8
...@@ -13,29 +13,31 @@ const Index = () => ( ...@@ -13,29 +13,31 @@ const Index = () => (
<strong>Important:</strong> exceptions in development mode take a <strong>Important:</strong> exceptions in development mode take a
different path than in production. These tests should be run on a different path than in production. These tests should be run on a
production build (i.e. 'next build').{' '} production build (i.e. 'next build').{' '}
<a href="https://nextjs.org/docs#custom-error-handling">Read more</a> <a href="https://nextjs.org/docs/advanced-features/custom-error-page#customizing-the-error-page">
Read more
</a>
</p> </p>
<ul> <ul>
<li>Server exceptions</li> <li>Server exceptions</li>
<ul> <ul>
<li> <li>
getInitialProps throws an Error. This should cause _error.js to render getServerSideProps throws an Error. This should cause _error.js to
and record Error('Client Test 1') in Sentry.{' '} render and record Error('Server Test 1') in Sentry.{' '}
<a href="/server/test1" target="_blank"> <a href="/server/test1" target="_blank">
Open in a new tab Open in a new tab
</a> </a>
</li> </li>
<li> <li>
getInitialProps returns a Promise that rejects. This should cause getServerSideProps returns a Promise that rejects. This should cause
_error.js to render and record Error('Server Test 2') in Sentry.{' '} _error.js to render and record Error('Server Test 2') in Sentry.{' '}
<a href="/server/test2" target="_blank"> <a href="/server/test2" target="_blank">
Open in a new tab Open in a new tab
</a> </a>
</li> </li>
<li> <li>
getInitialProps calls a Promise that rejects, but does not handle the getServerSideProps calls a Promise that rejects, but does not handle
rejection or await its result (returning synchronously). Sentry should the rejection or await its result (returning synchronously). Sentry
record Error('Server Test 3').{' '} should record Error('Server Test 3').{' '}
<a href="/server/test3" target="_blank"> <a href="/server/test3" target="_blank">
Open in a new tab Open in a new tab
</a> </a>
...@@ -52,51 +54,25 @@ const Index = () => ( ...@@ -52,51 +54,25 @@ const Index = () => (
<li>Client exceptions</li> <li>Client exceptions</li>
<ul> <ul>
<li>
getInitialProps throws an Error. This should cause _error.js to render
and record Error('Client Test 1') in Sentry. Note Sentry will double
count this exception. Once from an unhandledrejection and again in
_error.js. Could be a bug in Next.js or Sentry, requires more
debugging.{' '}
<Link href="/client/test1">
<a>Perform client side navigation</a>
</Link>
</li>
<li>
getInitialProps returns a Promise that rejects. This should cause
_error.js to render and record Error('Client Test 2') in Sentry. As
above, Sentry will double count this exception.{' '}
<Link href="/client/test2">
<a>Perform client side navigation</a>
</Link>
</li>
<li>
getInitialProps calls a Promise that rejects, but does not handle the
rejection or await its result (returning synchronously). Sentry should
record Error('Client Test 3').{' '}
<Link href="/client/test3">
<a>Perform client side navigation</a>
</Link>
</li>
<li> <li>
There is a top-of-module Promise that rejects, but its result is not There is a top-of-module Promise that rejects, but its result is not
awaited. Sentry should record Error('Client Test 4').{' '} awaited. Sentry should record Error('Client Test 1').{' '}
<Link href="/client/test4"> <Link href="/client/test1">
<a>Perform client side navigation</a> <a>Perform client side navigation</a>
</Link>{' '} </Link>{' '}
or{' '} or{' '}
<a href="/client/test4" target="_blank"> <a href="/client/test1" target="_blank">
Open in a new tab Open in a new tab
</a> </a>
</li> </li>
<li> <li>
There is a top-of-module exception. _error.js should render and record There is a top-of-module exception. _error.js should render and record
ReferenceError('process is not defined') in Sentry.{' '} ReferenceError('process is not defined') in Sentry.{' '}
<Link href="/client/test5"> <Link href="/client/test2">
<a>Perform client side navigation</a> <a>Perform client side navigation</a>
</Link>{' '} </Link>{' '}
or{' '} or{' '}
<a href="/client/test5" target="_blank"> <a href="/client/test2" target="_blank">
Open in a new tab Open in a new tab
</a> </a>
</li> </li>
...@@ -104,35 +80,35 @@ const Index = () => ( ...@@ -104,35 +80,35 @@ const Index = () => (
There is an exception during React lifecycle that is caught by There is an exception during React lifecycle that is caught by
Next.js's React Error Boundary. In this case, when the component Next.js's React Error Boundary. In this case, when the component
mounts. This should cause _error.js to render and record Error('Client mounts. This should cause _error.js to render and record Error('Client
Test 6') in Sentry.{' '} Test 3') in Sentry.{' '}
<Link href="/client/test6"> <Link href="/client/test3">
<a>Perform client side navigation</a> <a>Perform client side navigation</a>
</Link>{' '} </Link>{' '}
or{' '} or{' '}
<a href="/client/test6" target="_blank"> <a href="/client/test3" target="_blank">
Open in a new tab Open in a new tab
</a> </a>
</li> </li>
<li> <li>
There is an unhandled Promise rejection during React lifecycle. In There is an unhandled Promise rejection during React lifecycle. In
this case, when the component mounts. Sentry should record this case, when the component mounts. Sentry should record
Error('Client Test 7').{' '} Error('Client Test 4').{' '}
<Link href="/client/test7"> <Link href="/client/test4">
<a>Perform client side navigation</a> <a>Perform client side navigation</a>
</Link>{' '} </Link>{' '}
or{' '} or{' '}
<a href="/client/test7" target="_blank"> <a href="/client/test4" target="_blank">
Open in a new tab Open in a new tab
</a> </a>
</li> </li>
<li> <li>
An Error is thrown from an event handler. Sentry should record An Error is thrown from an event handler. Sentry should record
Error('Client Test 8').{' '} Error('Client Test 5').{' '}
<Link href="/client/test8"> <Link href="/client/test5">
<a>Perform client side navigation</a> <a>Perform client side navigation</a>
</Link>{' '} </Link>{' '}
or{' '} or{' '}
<a href="/client/test8" target="_blank"> <a href="/client/test5" target="_blank">
Open in a new tab Open in a new tab
</a> </a>
</li> </li>
......
import React from 'react'
const Test1 = () => <h1>Server Test 1</h1> const Test1 = () => <h1>Server Test 1</h1>
Test1.getInitialProps = () => { export function getServerSideProps() {
throw new Error('Server Test 1') throw new Error('Server Test 1')
} }
......
import React from 'react'
const Test2 = () => <h1>Server Test 2</h1> const Test2 = () => <h1>Server Test 2</h1>
Test2.getInitialProps = () => Promise.reject(new Error('Server Test 2')) export async function getServerSideProps() {
throw new Error('Server Test 2')
}
export default Test2 export default Test2
import React from 'react'
const Test3 = () => <h1>Server Test 3</h1> const Test3 = () => <h1>Server Test 3</h1>
Test3.getInitialProps = () => { export async function getServerSideProps() {
const doAsyncWork = () => Promise.reject(new Error('Server Test 3')) const doAsyncWork = () => Promise.reject(new Error('Server Test 3'))
doAsyncWork() doAsyncWork()
return {} return { props: {} }
} }
export default Test3 export default Test3
import React from 'react'
const doAsyncWork = () => Promise.reject(new Error('Server Test 4')) const doAsyncWork = () => Promise.reject(new Error('Server Test 4'))
doAsyncWork() doAsyncWork()
const Test4 = () => <h1>Server Test 4</h1> const Test4 = () => <h1>Server Test 4</h1>
// Define getInitialProps so that the page will be rendered on the server // Define getServerSideProps so that the page will be server rendered
// instead of statically // instead of statically generated
Test4.getInitialProps = () => { export async function getServerSideProps() {
return {} return { props: {} }
} }
export default Test4 export default Test4
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册