Add initial sentry setup
Related to #343 but will probably need a few more changes
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
makeMergedSchema,
|
||||
makeServices,
|
||||
} from '@redwoodjs/api'
|
||||
import { createSentryApolloPlugin } from 'src/lib/sentry'
|
||||
|
||||
import schemas from 'src/graphql/**/*.{js,ts}'
|
||||
import services from 'src/services/**/*.{js,ts}'
|
||||
@@ -16,6 +17,9 @@ export const handler = createGraphQLHandler({
|
||||
schemas,
|
||||
services: makeServices({ services }),
|
||||
}),
|
||||
plugins: [
|
||||
createSentryApolloPlugin(),
|
||||
],
|
||||
onException: () => {
|
||||
// Disconnect from your database with an unhandled exception.
|
||||
db.$disconnect()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { createUserInsecure } from 'src/services/users/users.js'
|
||||
import { db } from 'src/lib/db'
|
||||
import { sentryWrapper } from 'src/lib/sentry'
|
||||
import { enforceAlphaNumeric, generateUniqueString } from 'src/services/helpers'
|
||||
|
||||
export const handler = async (req, _context) => {
|
||||
const unWrappedHandler = async (req, _context) => {
|
||||
const body = JSON.parse(req.body)
|
||||
console.log(body)
|
||||
console.log(_context)
|
||||
@@ -82,3 +83,5 @@ export const handler = async (req, _context) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const handler = sentryWrapper(unWrappedHandler)
|
||||
|
||||
106
app/api/src/lib/sentry.ts
Normal file
106
app/api/src/lib/sentry.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import * as Sentry from '@sentry/node'
|
||||
import { context, Config, ApolloError } from '@redwoodjs/api'
|
||||
|
||||
let sentryInitialized = false
|
||||
if (process.env.SENTRY_DSN && !sentryInitialized) {
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
environment: process.env.CONTEXT,
|
||||
release: process.env.COMMIT_REF,
|
||||
})
|
||||
sentryInitialized = true
|
||||
}
|
||||
|
||||
async function reportError(error) {
|
||||
if (!sentryInitialized) return
|
||||
// If you do have authentication set up, we can add
|
||||
// some user data to help debug issues
|
||||
// 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 sentryWrapper = (handler) => async (event, lambdaContext) => {
|
||||
lambdaContext.callbackWaitsForEmptyEventLoop = false
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const callback = (err, result) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
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 createSentryApolloPlugin: Config['plugins'][number] = () => ({
|
||||
requestDidStart: () => {
|
||||
return {
|
||||
didEncounterErrors(ctx) {
|
||||
// If we couldn't parse the operation, don't
|
||||
// do anything here
|
||||
if (!ctx.operation) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const err of ctx.errors) {
|
||||
// Only report internal server errors,
|
||||
// all errors extending ApolloError should be user-facing
|
||||
if (err instanceof ApolloError) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add scoped report details and send to Sentry
|
||||
Sentry.withScope(scope => {
|
||||
// Annotate whether failing operation was query/mutation/subscription
|
||||
scope.setTag("kind", ctx.operation.operation);
|
||||
|
||||
// Log query and variables as extras (make sure to strip out sensitive data!)
|
||||
scope.setExtra("query", ctx.request.query);
|
||||
scope.setExtra("variables", ctx.request.variables);
|
||||
|
||||
if (err.path) {
|
||||
// We can also add the path as breadcrumb
|
||||
scope.addBreadcrumb({
|
||||
category: "query-path",
|
||||
message: err.path.join(" > "),
|
||||
level: Sentry.Severity.Debug
|
||||
});
|
||||
}
|
||||
|
||||
const transactionId = ctx.request.http.headers.get(
|
||||
"x-transaction-id"
|
||||
);
|
||||
if (transactionId) {
|
||||
scope.setTransaction(transactionId);
|
||||
}
|
||||
|
||||
Sentry.captureException(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user