diff --git a/package.json b/package.json index 888ec0c2cde5..25e310ba32de 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "packages/integrations", "packages/minimal", "packages/nextjs", + "packages/next-plugin-sentry", "packages/node", "packages/react", "packages/serverless", diff --git a/packages/next-plugin-sentry/LICENSE b/packages/next-plugin-sentry/LICENSE new file mode 100644 index 000000000000..2405b5588e48 --- /dev/null +++ b/packages/next-plugin-sentry/LICENSE @@ -0,0 +1,29 @@ +MIT License + +Copyright (c) 2021, Sentry +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/next-plugin-sentry/README.md b/packages/next-plugin-sentry/README.md new file mode 100644 index 000000000000..be8fac64345d --- /dev/null +++ b/packages/next-plugin-sentry/README.md @@ -0,0 +1,19 @@ +

+ + + +
+

+ +# Official Sentry SDK for NextJS + +TODO: npm version, npm dm, npm dt, typedoc + +## Links + +- [Official SDK Docs](https://docs.sentry.io/quickstart/) +- [TypeDoc](http://getsentry.github.io/sentry-javascript/) + +## Usage + +TODO diff --git a/packages/next-plugin-sentry/config.js b/packages/next-plugin-sentry/config.js new file mode 100644 index 000000000000..ede9e9c40227 --- /dev/null +++ b/packages/next-plugin-sentry/config.js @@ -0,0 +1,2 @@ +exports.serverConfig = {}; +exports.clientConfig = {}; diff --git a/packages/next-plugin-sentry/env.js b/packages/next-plugin-sentry/env.js new file mode 100644 index 000000000000..1c8d542dd50d --- /dev/null +++ b/packages/next-plugin-sentry/env.js @@ -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; diff --git a/packages/next-plugin-sentry/index.js b/packages/next-plugin-sentry/index.js new file mode 100644 index 000000000000..4ffa97645966 --- /dev/null +++ b/packages/next-plugin-sentry/index.js @@ -0,0 +1,10 @@ +const { serverConfig, clientConfig } = require('./config.js'); + +const Sentry = require('@sentry/nextjs'); +Sentry.showReportDialog = (...args) => { + Sentry._callOnClient('showReportDialog', ...args); +}; + +exports.Sentry = Sentry; +exports.serverConfig = serverConfig; +exports.clientConfig = clientConfig; diff --git a/packages/next-plugin-sentry/package.json b/packages/next-plugin-sentry/package.json new file mode 100644 index 000000000000..bc1a95ae3ae6 --- /dev/null +++ b/packages/next-plugin-sentry/package.json @@ -0,0 +1,38 @@ +{ + "name": "@sentry/next-plugin-sentry", + "version": "6.2.1", + "description": "Plugin for Next.js", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/next-plugin-sentry", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "main": "index.js", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sentry/nextjs": "6.2.1", + "@sentry/integrations": "6.2.1" + }, + "devDependencies": { + "eslint": "7.20.0", + "rimraf": "3.0.2" + }, + "nextjs": { + "name": "sentry", + "required-env": [] + }, + "peerDependencies": { + "next": "*" + }, + "scripts": { + "link:yarn": "yarn link", + "pack": "npm pack" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/next-plugin-sentry/src/on-error-client.js b/packages/next-plugin-sentry/src/on-error-client.js new file mode 100644 index 000000000000..4070d276c188 --- /dev/null +++ b/packages/next-plugin-sentry/src/on-error-client.js @@ -0,0 +1,14 @@ +import { withScope, captureException } from '@sentry/nextjs'; + +export default async function onErrorClient({ err, errorInfo, renderErrorProps, data, version }) { + // TODO: Extract some useful metadata from the router and other arguments — Kamil + + withScope(scope => { + if (typeof errorInfo?.componentStack === 'string') { + scope.setContext('react', { + componentStack: errorInfo.componentStack.trim(), + }); + } + captureException(err); + }); +} diff --git a/packages/next-plugin-sentry/src/on-error-server.js b/packages/next-plugin-sentry/src/on-error-server.js new file mode 100644 index 000000000000..4111adccc68d --- /dev/null +++ b/packages/next-plugin-sentry/src/on-error-server.js @@ -0,0 +1,23 @@ +import { captureException, flush, Handlers, withScope } from '@sentry/nextjs'; +import getConfig from 'next/config'; + +const { parseRequest } = Handlers; + +export default async function onErrorServer(err) { + const { serverRuntimeConfig = {}, publicRuntimeConfig = {} } = getConfig() || {}; + const sentryTimeout = serverRuntimeConfig.sentryTimeout || publicRuntimeConfig.sentryTimeout || 2000; + + withScope(scope => { + if (typeof err.req !== 'undefined') { + scope.addEventProcessor(event => + parseRequest(event, err.req, { + // 'cookies' and 'query_string' use `dynamicRequire` which has a bug in SSR envs right now — Kamil + request: ['data', 'headers', 'method', 'url'], + }), + ); + } + captureException(err instanceof Error ? err : err.err); + }); + + await flush(sentryTimeout); +} diff --git a/packages/next-plugin-sentry/src/on-init-client.js b/packages/next-plugin-sentry/src/on-init-client.js new file mode 100644 index 000000000000..c9ae987c9903 --- /dev/null +++ b/packages/next-plugin-sentry/src/on-init-client.js @@ -0,0 +1,17 @@ +import { init } from '@sentry/nextjs'; +import getConfig from 'next/config'; + +import { getDsn, getRelease } from '../env'; +import { clientConfig } from '../config'; + +export default async function initClient() { + const { publicRuntimeConfig = {} } = getConfig() || {}; + const runtimeConfig = publicRuntimeConfig.sentry || {}; + + init({ + dsn: getDsn(), + ...(getRelease() && { release: getRelease() }), + ...runtimeConfig, + ...clientConfig, + }); +} diff --git a/packages/next-plugin-sentry/src/on-init-server.js b/packages/next-plugin-sentry/src/on-init-server.js new file mode 100644 index 000000000000..92f50b79a0b2 --- /dev/null +++ b/packages/next-plugin-sentry/src/on-init-server.js @@ -0,0 +1,33 @@ +import { init } from '@sentry/nextjs'; +import { RewriteFrames } from '@sentry/integrations'; +import getConfig from 'next/config'; + +import { getDsn, getRelease } from '../env'; +import { serverConfig } from '../config'; + +export default async function initServer() { + const { serverRuntimeConfig = {}, publicRuntimeConfig = {} } = getConfig() || {}; + const runtimeConfig = serverRuntimeConfig.sentry || publicRuntimeConfig.sentry || {}; + + 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 || []), + ], + }); +} diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 3574a5517723..87abf2b5ff98 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -9,7 +9,6 @@ "engines": { "node": ">=6" }, - "main": "./dist/index.js", "module": "./esm/node.js", "browser": "./esm/browser.js", "types": "./dist/index.d.ts", @@ -21,6 +20,7 @@ "@sentry/minimal": "6.2.1", "@sentry/node": "6.2.1", "@sentry/react": "6.2.1", + "@sentry/next-plugin-sentry": "6.2.1", "@sentry/wizard": "1.2.0", "@sentry/webpack-plugin": "1.14.1" }, diff --git a/packages/nextjs/src/browser.ts b/packages/nextjs/src/browser.ts index 7c431144f3fd..a75cd6a1baa0 100644 --- a/packages/nextjs/src/browser.ts +++ b/packages/nextjs/src/browser.ts @@ -4,6 +4,8 @@ import { InitDecider } from './utils/initDecider'; import { MetadataBuilder } from './utils/metadataBuilder'; import { NextjsOptions } from './utils/nextjsOptions'; +export * from '@sentry/react'; + /** Inits the Sentry NextJS SDK on the browser with the React SDK. */ export function init(options: NextjsOptions): any { const metadataBuilder = new MetadataBuilder(options, ['nextjs', 'react']); @@ -16,5 +18,3 @@ export function init(options: NextjsOptions): any { console.log('[Sentry] Detected a non-production environment. Not initializing Sentry.'); } } - -export * from '@sentry/minimal'; diff --git a/packages/nextjs/src/node.ts b/packages/nextjs/src/node.ts index d17a6b2b5cd2..2e0528676034 100644 --- a/packages/nextjs/src/node.ts +++ b/packages/nextjs/src/node.ts @@ -4,6 +4,8 @@ import { InitDecider } from './utils/initDecider'; import { MetadataBuilder } from './utils/metadataBuilder'; import { NextjsOptions } from './utils/nextjsOptions'; +export * from '@sentry/node'; + /** Inits the Sentry NextJS SDK on node. */ export function init(options: NextjsOptions): any { const metadataBuilder = new MetadataBuilder(options, ['nextjs', 'node']); @@ -16,5 +18,3 @@ export function init(options: NextjsOptions): any { console.log('[Sentry] Detected a non-production environment. Not initializing Sentry.'); } } - -export * from '@sentry/minimal'; diff --git a/yarn.lock b/yarn.lock index 53d2d0b7e868..65e1f7b78a36 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3317,6 +3317,16 @@ "@sentry/utils" "6.2.1" tslib "^1.9.3" +"@sentry/integrations@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@sentry/integrations/-/integrations-6.2.1.tgz#caa9b49de29523698668d45827633be86b2268ff" + integrity sha512-UBvuil/b9M5HGH6aBDzTiIVRsmpC/wqwDKy28IO05XLdalmKgJ9C1EQhoyN6xw+1lINpXXFtfq4NhfgZgWbc7Q== + dependencies: + "@sentry/types" "6.2.1" + "@sentry/utils" "6.2.1" + localforage "^1.8.1" + tslib "^1.9.3" + "@sentry/minimal@6.2.1": version "6.2.1" resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.2.1.tgz#8f01480e1b56bc7dd54adf925e5317f233e19384"