-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Integrating Sentry with the t3 stack (v7 edition) #8500
-
This guide has an updated version
Important
Due to popular demand we have released an updated guide for version 8 of the Sentry Next.js SDK.
You can find the updated guide here.
This is a small guide on how to integrate Sentry with https://create.t3.gg/. A code repo of the guide can be found here: https://github.com/AbhiPrasad/sentry-t3-app
Was initially requested here.
Initial Setup
First, create your test app with npm create t3-app@latest
.
Next, follow our Next.js SDK setup guide by running npx @sentry/wizard@latest -i nextjs
. This will configure and install our Next.js SDK with the appropriate Sentry project.
You can test this out by running npm run dev
and checking out http://localhost:3000/sentry-example-page
Screenshot 2023年07月11日 at 9 56 24 AM
Now depending on what you've added, use our optional integrations/setup.
Optional setup
Prisma
If you've configured prisma, import the prisma client from ~/server/db
and add the Sentry Prisma integration to your sentry.server.config.ts
file.
// sentry.server.config.ts import * as Sentry from "@sentry/nextjs"; import { prisma } from "~/server/db"; Sentry.init({ dsn: "https://e8f2595939e24d54a6da461c3d55a9ab@o1123544.ingest.sentry.io/4504791357652992", // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, integrations: [ new Sentry.Integrations.Prisma({ client: prisma, }) ], // Setting this option to true will print useful information to the console while you're setting up Sentry. debug: false, });
nextAuth
If you've set up nextAuth
, you can configure it's events to attach information to Sentry. For example below I've updated src/server/auth.ts
to set the user on sign in. You can also create Sentry breadcrumbs on the different events emitted.
diff --git a/src/server/auth.ts b/src/server/auth.ts index e3f5444..9759c7d 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -8,6 +8,7 @@ import { import DiscordProvider from "next-auth/providers/discord"; import { env } from "~/env.mjs"; import { prisma } from "~/server/db"; +import * as Sentry from "@sentry/nextjs"; /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` @@ -45,6 +46,14 @@ export const authOptions: NextAuthOptions = { }, }), }, + events: { + signIn({ user }) { + Sentry.setUser({ id: user.id }); + }, + signOut() { + Sentry.setUser(null); + }, + }, adapter: PrismaAdapter(prisma), providers: [ DiscordProvider({
trpc
If you're using tRPC, you can set up Sentry's tRPC integration by updating src/server/api/trpc.ts
to extend the trpc middleware.
diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index c2491f0..2540ad5 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -14,6 +14,7 @@ import superjson from "superjson"; import { ZodError } from "zod"; import { getServerAuthSession } from "~/server/auth"; import { prisma } from "~/server/db"; +import * as Sentry from "@sentry/nextjs"; /** * 1. CONTEXT @@ -119,6 +120,14 @@ const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { }); }); +const sentryMiddleware = t.middleware( + Sentry.Handlers.trpcMiddleware({ + attachRpcInput: true, + }) +); + +const finalMiddleware = sentryMiddleware.unstable_pipe(enforceUserIsAuthed); + /** * Protected (authenticated) procedure * @@ -127,4 +136,4 @@ const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { * * @see https://trpc.io/docs/procedures */ -export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); +export const protectedProcedure = t.procedure.use(finalMiddleware);
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 7 -
❤️ 6
Replies: 6 comments 37 replies
-
@AbhiPrasad I followed your instructions, but unfortunately my errors still aren't getting automatically intercepted form TRPC. Do you have any recommendations?
NOTE: I replaced sensitive values. Also, errors from my frontend are properly getting intercepted
// sentry.server.config.ts // This file configures the initialization of Sentry on the server. // The config you add here will be used whenever the server handles a request. // https://docs.sentry.io/platforms/javascript/guides/nextjs/ import * as Sentry from "@sentry/nextjs"; import { prisma } from "~/server/db"; Sentry.init({ dsn: "fake dsn", // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, integrations: [ new Sentry.Integrations.Prisma({ client: prisma, }), ], // Setting this option to true will print useful information to the console while you're setting up Sentry. debug: false, });
// auth.ts import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { Community, Role } from "@prisma/client"; import { type GetServerSidePropsContext } from "next"; import { getServerSession, type DefaultSession, type NextAuthOptions, DefaultUser, } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; import { env } from "~/env.mjs"; import { prisma } from "~/server/db"; import * as Sentry from "@sentry/nextjs"; /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` * object and keep type safety. * * @see https://next-auth.js.org/getting-started/typescript#module-augmentation */ declare module "next-auth" { interface Session extends DefaultSession { user: { id: string; role: Role; isManager: boolean; isHost: boolean; } & DefaultSession["user"]; } interface User extends DefaultUser { role: Role; isManager: boolean; isHost: boolean; } } /** * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. * * @see https://next-auth.js.org/configuration/options */ export const authOptions: NextAuthOptions = { callbacks: { session: async ({ session, user }) => { let userDetails = await prisma.user.findUnique({ where: { id: user.id, }, include: { managedCommunities: true, hostedEvents: true, Event: true, }, }); return { ...session, user: { ...session.user, id: user.id, role: user.role, isManager: !!userDetails?.managedCommunities.length, isHost: !!userDetails?.hostedEvents.length || !!userDetails?.Event.length, }, }; }, }, events: { signIn({ user }) { Sentry.setUser({ id: user.id }); }, }, adapter: PrismaAdapter(prisma), providers: [ GoogleProvider({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, }), /** * ...add more providers here. * * Most other providers require a bit more work than the Discord provider. For example, the * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account * model. Refer to the NextAuth.js docs for the provider you want to use. Example: * * @see https://next-auth.js.org/providers/github */ ], }; /** * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file. * * @see https://next-auth.js.org/configuration/nextjs */ export const getServerAuthSession = (ctx: { req: GetServerSidePropsContext["req"]; res: GetServerSidePropsContext["res"]; }) => { return getServerSession(ctx.req, ctx.res, authOptions); };
// trpc.ts /** * YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS: * 1. You want to modify request context (see Part 1). * 2. You want to create a new middleware or type of procedure (see Part 3). * * TL;DR - This is where all the tRPC server stuff is created and plugged in. The pieces you will * need to use are documented accordingly near the end. */ import * as Sentry from "@sentry/nextjs"; import { initTRPC, TRPCError } from "@trpc/server"; import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; import { type Session } from "next-auth"; import superjson from "superjson"; import { ZodError } from "zod"; import { getServerAuthSession } from "~/server/auth"; import { prisma } from "~/server/db"; import { s3 } from "../aws/s3"; /** * 1. CONTEXT * * This section defines the "contexts" that are available in the backend API. * * These allow you to access things when processing a request, like the database, the session, etc. */ /** * This helper generates the "internals" for a tRPC context. If you need to use it, you can export * it from here. * * Examples of things you may need it for: * - testing, so we don't have to mock Next.js' req/res * - tRPC's `createSSGHelpers`, where we don't have req/res * * @see https://create.t3.gg/en/usage/trpc#-serverapitrpcts */ const createInnerTRPCContext = (opts: CreateContextOptions) => { return { session: opts.session, prisma, s3, }; }; /** * This is the actual context you will use in your router. It will be used to process every request * that goes through your tRPC endpoint. * * @see https://trpc.io/docs/context */ export const createTRPCContext = async (opts: CreateNextContextOptions) => { const { req, res } = opts; // Get the session from the server using the getServerSession wrapper function const session = await getServerAuthSession({ req, res }); return createInnerTRPCContext({ session, }); }; /** * 2. INITIALIZATION * * This is where the tRPC API is initialized, connecting the context and transformer. We also parse * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation * errors on the backend. */ const t = initTRPC.context<typeof createTRPCContext>().create({ transformer: superjson, errorFormatter({ shape, error }) { return { ...shape, data: { ...shape.data, zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, }, }; }, }); /** * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) * * These are the pieces you use to build your tRPC API. You should import these a lot in the * "/src/server/api/routers" directory. */ /** * This is how you create new routers and sub-routers in your tRPC API. * * @see https://trpc.io/docs/router */ export const createTRPCRouter = t.router; /** * Public (unauthenticated) procedure * * This is the base piece you use to build new queries and mutations on your tRPC API. It does not * guarantee that a user querying is authorized, but you can still access user session data if they * are logged in. */ export const publicProcedure = t.procedure; /** Reusable middleware that enforces users are logged in before running the procedure. */ const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { if (!ctx.session || !ctx.session.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ ctx: { // infers the `session` as non-nullable session: { ...ctx.session, user: ctx.session.user }, }, }); }); type CreateContextOptions = { session: Session | null; }; const sentryMiddleware = t.middleware( Sentry.Handlers.trpcMiddleware({ attachRpcInput: true, }) ); const finalMiddleware = sentryMiddleware.unstable_pipe(enforceUserIsAuthed); /** * Protected (authenticated) procedure * * If you want a query or mutation to ONLY be accessible to logged in users, use this. It verifies * the session is valid and guarantees `ctx.session.user` is not null. * * @see https://trpc.io/docs/procedures */ export const protectedProcedure = t.procedure.use(finalMiddleware);
Beta Was this translation helpful? Give feedback.
All reactions
-
@joshsny (削除) I'm also been experiencing this. Did you find a solution for this problem? (削除ここまで) [resolved]
I've done the following:
// file: `packages/core/src/server/api/trpc.ts` /** Reusable middleware that enforces users are logged in before running the procedure. */ const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { if (!ctx.session?.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); } // NOTE: Configure the user in Sentry Sentry.setUser({ id: ctx.session.user.id, email: ctx.session.user.email ?? undefined, }); return next({ ctx: { // infers the `session` as non-nullable session: { ...ctx.session, user: ctx.session.user }, }, }); });
Side note: Its seems like some stuff have changed, since the SDK upgrade to v8 as well ...
Beta Was this translation helpful? Give feedback.
All reactions
-
@hilibari Yes please upgrade the SDK to the latest version. If this is still a problem, please open a github issue with minimal reproduction. Thank you!
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
@lforst - I've resolved the issue above. I followed your suggestion of calling Sentry.setUser
within the middleware (instead of repetitively calling Sentry.setUser
within each tRPC route)! 🙏
Big thanks for the continued support on this discussion!
Beta Was this translation helpful? Give feedback.
All reactions
-
@AbhiPrasad – Just like @b1rdex, I also had to add Sentry.captureException(error)
within my onError
. Maybe this has regressed in the latest v8 update? In any case, it wasn't working for me.
P.S. Thanks for the continued support on throughout this discussion, as well!!
Beta Was this translation helpful? Give feedback.
All reactions
-
@hilibari Can you share a snippet of where exactly are you calling .setUser
. Maybe this has regressed in the latest v8 update? In any case, it wasn't working for me.
You mean It doesn't work without adding Sentry.captureException(error)
on the onError
or it doesn't work at all ?
Beta Was this translation helpful? Give feedback.
All reactions
-
Hey @AbhiPrasad thank you for this nice guide, and (i assume) for the docs section on trpc :)
I've stumbled upon a problem while trying this on my T3 project. In your guide, you've used a Sentry import from @sentry/nextjs
, which results in a warning during build both locally and on Vercel:
Creating an optimized production build ...
⚠ Compiled with warnings
./src/server/api/trpc.ts
Attempted import error: 'Handlers' is not exported from '@sentry/nextjs' (imported as 'Sentry').
Import trace for requested module:
./src/server/api/trpc.ts
./src/server/api/root.ts
./src/app/api/trpc/[trpc]/route.ts
./node_modules/next/dist/build/webpack/loaders/next-app-loader.js?name=app%2Fapi%2Ftrpc%2F%5Btrpc%5D%2Froute&page=%2Fapi%2Ftrpc%2F%5Btrpc%5D%2Froute&pagePath=private-next-app-dir%2Fapi%2Ftrpc%2F%5Btrpc%5D%2Froute.ts&appDir=%2FUsers%2Fmarkomitranic%2FSites%2Fnoa%2Fecomm-wineandbarrels%2Fsrc%2Fapp&appPaths=%2Fapi%2Ftrpc%2F%5Btrpc%5D%2Froute&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&preferredRegion=&middlewareConfig=e30%3D!./src/app/api/trpc/[trpc]/route.ts?__next_edge_ssr_entry__
On the other hand, the aforementioned docs import it from @sentry/node
and I've tried that as well, but it then fails with missing dependency errors for node stlib packages like domain, fs etc.
Do you have any advice for how to handle this please? :)
Beta Was this translation helpful? Give feedback.
All reactions
-
@markomitranic can you share all the potentially relevant code in trpc.ts
. Ideally you open a new issue for this.
Beta Was this translation helpful? Give feedback.
All reactions
-
Hey @lforst I don’t think that makes sense, as this has jothing to do with my code.
As I wrote above, the sentry package has been updated and no longer seems compatibile with the guide, so I asked for a refreshhed version of the guide.
Beta Was this translation helpful? Give feedback.
All reactions
-
I think it makes a lot of sense. The error message you're getting states that Handlers
is not exported from @sentry/nextjs
. That should however be absolutely the case. No upgrading of the guide will help there.
Beta Was this translation helpful? Give feedback.
All reactions
-
Thank you for your response @lforst unfortunately you are contradicting what the guide itself says.
The guide clearly states that an import from @sentry/nextjs must be added +import * as Sentry from "@sentry/nextjs";
However, the very next place the import is used in the same file in the guide, it invokes Handlers
, which no longer exists in the Sentry module. This is percisely the line that fails, with the error I've posted above, as Handlers is not exported anymore.
+const sentryMiddleware = t.middleware(
+ Sentry.Handlers.trpcMiddleware({
+ attachRpcInput: true,
+ })
+);
Here is a screenshot from the exact part of the guide, that contains the offending code:
Screenshot 2024年03月27日 at 09 39 18
Beta Was this translation helpful? Give feedback.
All reactions
-
Apparently, doing the following removes the warning during build process, on Next 14.1.4
const { Handlers } = Sentry; const sentryMiddleware = t.middleware( Handlers.trpcMiddleware({ attachRpcInput: true }), );
I am not smart enough to know why this would somehow magically ensure that Handlers
is available vs using Sentry.Handlers
(I suspect it has something to do with tree-shaking), but can replicate this without question. Hope this helps someone :P
Beta Was this translation helpful? Give feedback.
All reactions
-
Hey @AbhiPrasad , excellent guide! I was trying to integrate Sentry with next TRPc, which is v11(it's a default in create t3 app now) and I think some types got incompatible in the meantime.
Screenshot 2024年03月26日 at 18 48 55Argument of type '<T>({ path, type, next, rawInput }: TrpcMiddlewareArguments<T>) => T' is not assignable to parameter of type 'MiddlewareFunction<{ headers: Headers; db: any; session: Session | null; }, object, object, unknown, unknown>'.
Types of parameters '__0' and 'opts' are incompatible.
Property 'rawInput' is missing in type '{ ctx: { session: Session | null; headers: Headers; db: any; }; type: "query" | "mutation" | "subscription"; path: string; input: unknown; getRawInput: GetRawInputFn; meta: object | undefined; next: { ...; }; }' but required in type 'TrpcMiddlewareArguments<Promise<MiddlewareResult<unknown>>>'.
do you have an idea of a quick solution to this? obv something that will not require the changes in SDK.
thanks a lot for the great effort writing this & answering!
Beta Was this translation helpful? Give feedback.
All reactions
-
@lforst good, so the guide should, after all, be updated with a ts ignore? Also with any additional information about the plans to fix the busted type, I suppose?
Beta Was this translation helpful? Give feedback.
All reactions
-
We should honestly just fix the type of that function, it's just not a prio right now. PRs welcome!
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 1
-
@lforst I was more curios about other things. Namely setting user context in the Next.js app. I read this: #10019
As far as I understood I would need to put sentry.setUser() into every API route, have useEffect() on frontend for the same reason. Now I am struggling a bit to understand what would be the best way to register user in TRPC errors :D
That was the example I was looking for, @ts-ignore
was obvious haha
Beta Was this translation helpful? Give feedback.
All reactions
-
for context that's what I am trying to do to test it:
import { z } from "zod"; import { createTRPCRouter, protectedProcedure, publicProcedure, } from "~/server/api/trpc"; export const postRouter = createTRPCRouter({ hello: publicProcedure .input(z.object({ text: z.string() })) .query(({ input }) => { // Throw an unhandled error unconditionally throw new Error("Simulated unhandled error in hello route"); return { greeting: `Hello ${input.text}`, }; }), });
probably still missing something, but ideally error would be captured by sentry and I could enrich it with user data
Beta Was this translation helpful? Give feedback.
All reactions
-
Generally yes, right now your have to call setUser() in every route individually. However, if you are using tRPC you could also create a custom middleware that calls setUser().
I am doing it in my auth middleware:
const authMiddleware = t.middleware(async ({ ctx, next }) => { if (ctx.userId === null) { throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Authentication failed.' }); } Sentry.setUser({ id: ctx.userId }); return next({ ctx: { userId: ctx.userId, }, }); });
Beta Was this translation helpful? Give feedback.
All reactions
-
Hey folks - with the release of 8.x
of our JavaScript SDKs, the guide here changes a little bit. I will update this sometime this week!
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 7 -
👀 5
-
Thanks Prasad!!
Beta Was this translation helpful? Give feedback.
All reactions
-
Hi @AbhiPrasad, I've been eagerly waiting for this and there's lot of upvotes too. Could we please get the updated guide?
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 2
-
@AbhiPrasad also waiting for this here.
Beta Was this translation helpful? Give feedback.
All reactions
-
Updated guide is here! #13103
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 1
-
@AbhiPrasad Maybe Update the prisma setup guide to match the latest documentation, https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/prisma/
Beta Was this translation helpful? Give feedback.
All reactions
-
Hi, due to popular demand we have released an updated version of this guide for version 8 of the Next.js SDK: #13103
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 2