[Guide] Setting up Sentry with Redwood (envelop version)

This entire post is written by @pi0neerpat - all credit goes to him. I’m reposting in a separate thread so that it is more easily searchable. Original post here: Using GraphQL Envelop+Helix in Redwood v0.35+ - #28 by dthyresson

As of v0.37, Redwood made the switch to using The Guild’s Envelop. As a result setting up plugins has become substantially easier! This is now the defacto way of setting up Sentry, and likely the easiest one too!


Docs for using Sentry.io with redwood

Install the required libraries:

yarn add @envelop/sentry @sentry/node @sentry/tracing

Create a reusable Sentry file api/src/lib/sentry

import * as Sentry from '@sentry/node'
import * as Tracing from '@sentry/tracing'

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  tracesSampleRate: 0.5,
})

export default Sentry

Then import it into your graphql function api/src/functions/graphql

import { createGraphQLHandler } from '@redwoodjs/graphql-server'
import { useSentry } from '@envelop/sentry'
import Sentry from 'src/lib/sentry'

import { getCurrentUser } from 'src/lib/auth'
import { logger } from 'src/lib/logger'
import directives from 'src/directives/**/*.{js,ts}'
import sdls from 'src/graphql/**/*.sdl.{js,ts}'
import services from 'src/services/**/*.{js,ts}'
import { db } from 'src/lib/db'

const extraPlugins = [
  useSentry({
    includeRawResult: false, // set to `true` in order to include the execution result in the metadata collected
    includeResolverArgs: false, // set to `true` in order to include the args passed to resolvers
    includeExecuteVariables: false, // set to `true` in order to include the operation variables values
    // appendTags: (args) => { return { ... }} // if you wish to add custom "tags" to the Sentry transaction created per operation
  }),
]

export const handler = createGraphQLHandler({
  getCurrentUser,
  loggerConfig: {
    logger,
    options: { tracing: true, operationName: true },
  },
  directives,
  sdls,
  services,
  extraPlugins,
  onException: () => {
    db.$disconnect()
  },
})

For a custom function, such as api/src/functions/checkout

import Sentry from 'src/lib/sentry'
import { processOrder } from 'src/lib/wyre/order'

export const handler = async (event) => {
  // Default response
  let statusCode = 200
  let message = ''

  try {
    let body = event.body
    if (typeof body === 'string') body = JSON.parse(body)

    await processOrder({ orderId: body.orderId, referenceId: body.referenceId })

    return {
      statusCode,
      body: {
        message: 'Success!',
      },
    }
  } catch (e) {
    console.log(event)
    Sentry.captureException(e)
    return {
      statusCode, // Always return 200
      body: {
        message: 'Internal server error',
      },
    }
  }
}

:tada: Tada! Please share your experience if you use Sentry or other plugins

PS extra goodness in getCurrentUser

//api/src/lib/auth.js
import Sentry from 'src/lib/sentry'

export const getCurrentUser = async (session) => {
  const member = await db.member.findUnique({ where: { id: session.id } })
  if (!member) return null
  // WARNING: Returned values here are exposed to the FE
  Sentry.setUser({ username: member.username, id: member.id })
1 Like