Skip to content

Commit

Permalink
Add setup sentry command (#7790)
Browse files Browse the repository at this point in the history
* created Sentry setup command and templates

* documented the setup sentry command

* added opinionated defaults to Sentry Envelop plugin

* relocated setup sentry handler to separate file

* added note about SentryLayout useEffect dependency array

* move cmd to `exp setup-sentry`

---------

Co-authored-by: David Price <thedavid@thedavidprice.com>
  • Loading branch information
2 people authored and jtoar committed May 11, 2023
1 parent e5acc46 commit 3184bf2
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 0 deletions.
23 changes: 23 additions & 0 deletions packages/cli/src/commands/experimental/setupSentry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { getEpilogue } from './util'

export const command = 'setup-sentry'

export const description = 'Setup Sentry error and performance tracking'

export const EXPERIMENTAL_TOPIC_ID = 4880

export const builder = (yargs) => {
yargs
.option('force', {
alias: 'f',
default: false,
description: 'Overwrite existing sentry.js config files',
type: 'boolean',
})
.epilogue(getEpilogue(command, description, EXPERIMENTAL_TOPIC_ID, true))
}

export const handler = async (options) => {
const { handler } = await import('./setupSentryHandler')
return handler(options)
}
200 changes: 200 additions & 0 deletions packages/cli/src/commands/experimental/setupSentryHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import fs from 'fs'
import path from 'path'

import { Listr } from 'listr2'

import {
addApiPackages,
addEnvVarTask,
addWebPackages,
colors as c,
getPaths,
isTypeScriptProject,
prettify,
writeFilesTask,
} from '@redwoodjs/cli-helpers'
import { getConfigPath } from '@redwoodjs/project-config'
import { errorTelemetry } from '@redwoodjs/telemetry'

import { writeFile } from '../../lib'

const PATHS = getPaths()

export const handler = async ({ force }) => {
const extension = isTypeScriptProject ? 'ts' : 'js'

const notes = []

const tasks = new Listr([
addApiPackages([
'@envelop/sentry@5',
'@sentry/node@7',
'@sentry/tracing@7',
]),
addWebPackages(['@sentry/react@7', '@sentry/tracing@7']),
addEnvVarTask(
'SENTRY_DSN',
'https://XXXXXXX@XXXXXXX.ingest.sentry.io/XXXXXXX',
'https://docs.sentry.io/product/sentry-basics/dsn-explainer/'
),
{
title: 'Setting up Sentry on the API and web sides',
task: () =>
writeFilesTask(
{
[path.join(PATHS.api.lib, `sentry.${extension}`)]: fs
.readFileSync(
path.join(__dirname, 'templates/sentryApi.ts.template')
)
.toString(),
[path.join(PATHS.web.src, 'lib', `sentry.${extension}`)]: fs
.readFileSync(
path.join(__dirname, 'templates/sentryWeb.ts.template')
)
.toString(),
},
{ existingFiles: force ? 'OVERWRITE' : 'SKIP' }
),
},
{
title: 'Implementing the Envelop plugin',
task: (ctx) => {
const graphqlHandlerPath = path.join(
PATHS.api.functions,
`graphql.${extension}`
)

const contentLines = fs
.readFileSync(graphqlHandlerPath)
.toString()
.split('\n')

const handlerIndex = contentLines.findLastIndex((line) =>
/^export const handler = createGraphQLHandler\({/.test(line)
)

const pluginsIndex = contentLines.findLastIndex((line) =>
/extraPlugins:/.test(line)
)

if (handlerIndex === -1 || pluginsIndex !== -1) {
ctx.addEnvelopPluginSkipped = true
return
}

contentLines.splice(
handlerIndex,
1,
"import 'src/lib/sentry'",
'',
'export const handler = createGraphQLHandler({',
'extraPlugins: [useSentry({',
'includeRawResult: true,',
'includeResolverArgs: true,',
'includeExecuteVariables: true,',
'})],'
)

contentLines.splice(0, 0, "import { useSentry } from '@envelop/sentry'")

fs.writeFileSync(
graphqlHandlerPath,
prettify('graphql.ts', contentLines.join('\n'))
)
},
},
{
title: "Replacing Redwood's Error boundary",
task: () => {
const contentLines = fs
.readFileSync(PATHS.web.app)
.toString()
.split('\n')

const webImportIndex = contentLines.findLastIndex((line) =>
/^import { FatalErrorBoundary, RedwoodProvider } from '@redwoodjs\/web'$/.test(
line
)
)
contentLines.splice(
webImportIndex,
1,
"import { RedwoodProvider } from '@redwoodjs/web'"
)

const boundaryOpenIndex = contentLines.findLastIndex((line) =>
/<FatalErrorBoundary page={FatalErrorPage}>/.test(line)
)
contentLines.splice(
boundaryOpenIndex,
1,
'<Sentry.ErrorBoundary fallback={FatalErrorPage}>'
)

const boundaryCloseIndex = contentLines.findLastIndex((line) =>
/<\/FatalErrorBoundary>/.test(line)
)
contentLines.splice(boundaryCloseIndex, 1, '</Sentry.ErrorBoundary>')

contentLines.splice(0, 0, "import Sentry from 'src/lib/sentry'")

fs.writeFileSync(
PATHS.web.app,
prettify('App.tsx', contentLines.join('\n'))
)
},
},
{
title: 'Adding config to redwood.toml...',
task: (_ctx, task) => {
const redwoodTomlPath = getConfigPath()
const configContent = fs.readFileSync(redwoodTomlPath, 'utf-8')
if (!configContent.includes('[experimental.sentry]')) {
// Use string replace to preserve comments and formatting
writeFile(
redwoodTomlPath,
configContent.concat(`\n[experimental.sentry]\n\tenabled = true\n`),
{
overwriteExisting: true, // redwood.toml always exists
}
)
} else {
task.skip(
`The [experimental.sentry] config block already exists in your 'redwood.toml' file.`
)
}
},
},
{
title: 'One more thing...',
task: (ctx) => {
notes.push(
c.green(
'You will need to add `SENTRY_DSN` to `includeEnvironmentVariables` in redwood.toml.'
)
)

if (ctx.addEnvelopPluginSkipped) {
notes.push(
`${c.underline(
'Make sure you implement the Sentry Envelop plugin:'
)} https://redwoodjs.com/docs/cli-commands#sentry-envelop-plugin`
)
} else {
notes.push(
"Check out RedwoodJS' docs for more: https://redwoodjs.com/docs/cli-commands#setup-sentry"
)
}
},
},
])

try {
await tasks.run()
console.log(notes.join('\n'))
} catch (e) {
errorTelemetry(process.argv, e.message)
console.error(c.error(e.message))
process.exit(e?.exitCode || 1)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as Sentry from '@sentry/node'
import * as Tracing from '@sentry/tracing'

import { db as client } from 'src/lib/db'

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
integrations: [new Tracing.Integrations.Prisma({ client })],
tracesSampleRate: 1.0,
})

export default Sentry
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Sentry from '@sentry/react'
import { BrowserTracing } from '@sentry/tracing'

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
integrations: [new BrowserTracing()],
tracesSampleRate: 1.0,
})

export default Sentry

0 comments on commit 3184bf2

Please sign in to comment.