Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Setting up Sentry in RedwoodJS
// api/src/functions/exampleFunction.js
import { db } from 'src/lib/db'
import { wrapFunction } from 'src/lib/sentry'
export const handler = wrapFunction(async (event, context) => {
return {
statusCode: 200,
body: 'Hello world',
}
})
// web/src/components/FatalErrorBoundary/FatalErrorBoundary.js
import { FatalErrorBoundary as FatalErrorBoundaryBase } from '@redwoodjs/web'
import * as Sentry from '@sentry/browser'
class FatalErrorBoundary extends FatalErrorBoundaryBase {
componentDidCatch(error, errorInfo) {
Sentry.withScope((scope) => {
scope.setExtras(errorInfo)
Sentry.captureException(error)
})
}
}
export default FatalErrorBoundary
// api/src/functions/graphql.js
import {
createGraphQLHandler,
makeMergedSchema,
makeServices,
} from '@redwoodjs/api'
import importAll from '@redwoodjs/api/importAll.macro'
import { db } from 'src/lib/db'
import { wrapFunction, wrapServices } from 'src/lib/sentry'
const schemas = importAll('api', 'graphql')
const services = importAll('api', 'services')
export const handler = wrapFunction(
createGraphQLHandler({
schema: makeMergedSchema({
schemas,
services: wrapServices(makeServices({ services })),
}),
db,
})
)
// web/src/index.js
import { AuthProvider } from '@redwoodjs/auth'
import ReactDOM from 'react-dom'
import { RedwoodProvider } from '@redwoodjs/web'
import * as Sentry from '@sentry/browser'
import FatalErrorPage from 'src/pages/FatalErrorPage'
import FatalErrorBoundary from 'src/components/FatalErrorBoundary'
import Routes from 'src/Routes'
import './index.css'
if (process.env.REDWOOD_ENV_SENTRY_DSN) {
Sentry.init({
dsn: process.env.REDWOOD_ENV_SENTRY_DSN,
})
}
ReactDOM.render(
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider>
<Routes />
</RedwoodProvider>
</FatalErrorBoundary>,
document.getElementById('redwood-app')
)
[build]
command = "yarn rw db up --no-db-client && yarn rw build"
publish = "web/dist"
functions = "api/dist/functions"
[dev]
command = "yarn rw dev"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[plugins]]
package = 'netlify-plugin-prisma-provider'
[plugins.inputs]
path = 'api/prisma/schema.prisma'
[[plugins]]
package = "@sentry/netlify-build-plugin"
// api/src/lib/sentry.js
import * as Sentry from '@sentry/node'
import { context } from '@redwoodjs/api'
let sentryInitialized = false
if (process.env.REDWOOD_ENV_SENTRY_DSN && !sentryInitialized) {
Sentry.init({
dsn: process.env.REDWOOD_ENV_SENTRY_DSN,
environment: process.env.CONTEXT,
release: process.env.COMMIT_REF,
})
sentryInitialized = true
}
async function reportError(error) {
if (!sentryInitialized) return
if (context.currentUser) {
Sentry.configureScope((scope) => {
scope.setUser({
id: context.currentUser.id,
email: context.currentUser.email,
})
})
}
if (typeof error === 'string') {
Sentry.captureMessage(error)
} else {
Sentry.captureException(error)
}
await Sentry.flush()
}
export const wrapFunction = (handler) => async (event, lambdaContext) => {
lambdaContext.callbackWaitsForEmptyEventLoop = false
try {
return await new Promise((resolve, reject) => {
const callback = (err, result) => {
if (err) reject(err)
resolve(result)
}
const resp = handler(event, lambdaContext, callback)
if (resp?.then) {
resp.then(resolve, reject)
}
})
} catch (e) {
// This catches both sync errors & promise
// rejections, because we 'await' on the handler
await reportError(e)
throw e
}
}
export const wrapServiceFunction = (fn) => async (...args) => {
try {
return await fn(...args)
} catch (e) {
await reportError(e)
throw e
}
}
export const wrapService = (service) => {
return Object.keys(service).reduce((acc, key) => {
return {
...acc,
[key]: wrapServiceFunction(service[key]),
}
}, {})
}
export const wrapServices = (services) => {
return Object.keys(services).reduce((acc, key) => {
return {
...acc,
[key]: wrapService(services[key]),
}
}, {})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.