-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
@sentry/nextjs not reporting server side errors on Netlify #3880
Description
- Review the documentation: https://docs.sentry.io/
- Search for existing issues: https://github.com/getsentry/sentry-javascript/issues
- Use the latest release: https://github.com/getsentry/sentry-javascript/releases
- Provide a link to the affected event from your Sentry account
Package + Version
-
@sentry/browser -
@sentry/node -
raven-js -
raven-node(raven for node) - other:
@sentry/nextjs
Version:
6.11.0
Description
I am currently trying to integrate Sentry into a Next.js application (version 11.0.1) running on Netlify
(using the official @netlify/plugin-nextjs in version 3.8.0).
I know that Netlify is not the "official" runtime for Next.js, but I think there are a lot of people out there running Next.js on Netlify.
On the client side everything works as expected, but when it comes to server side rendering or API routes, it seems that no events are sent to the Sentry API at all. On first glance it looked like the same issue as #3643, but that is only partially the case, as far as I can tell.
API Routes
In the case of API routes the problem seems to be very similar to #3643. The cause seems to be that Sentry.flush() is not getting called.
I spent some time trying to find the cause of this, and it seems like Sentry expects the ServerResponse.end method to always get called (see https://github.com/getsentry/sentry-javascript/blob/6.11.0/packages/nextjs/src/utils/withSentry.ts#L22). This seems to hold for Vercel deployments, but it is not the case for Netlify deployments.
From what I can tell, @netlify/plugin-nextjs wraps the Next.js API routes into a compatibility layer, so they can be executed within AWS Lambda. To achieve this the req and res parameters which are passed to the API route handler function are just wrappers for the underlying event that was received by the AWS lambda handler. There is no code which ensures the end method gets called on the response. Thus the Sentry events are not flushed before the function invocation terminates.
Have a look at the relevant code of @netlify/plugin-nextjs for more details: https://github.com/netlify/netlify-plugin-nextjs/blob/v3.8.0/src/lib/templates/getHandlerFunction.js#L166
Server Side Rendering (getServerSideProps)
It took me a while to understand, that instrumenting getServerSideProps is not supported as of now, but maybe someone having the same issue might benefit from what I discovered. In any case the documentation should be updated to reflect this, because server side rendering is an integral part of Next.js. People expect this to work, because otherwise the Sentry integration with Next.js is not nearly as useful as it could be.
The main problem is that the sentry.server.config.js snippet is not being injected into the code that is emitted by Next.js/webpack for SSR functions. As you can see here, it will only ever be injected into API routes and the custom App override: https://github.com/getsentry/sentry-javascript/blob/6.11.0/packages/nextjs/src/config/webpack.ts#L230.
I am not sure whether this also is a Netlify specific problem, because Netlify packages SSR functions just like API routes as standalone chunks of code with a small AWS lambda compatibility layer. Since the sentry.server.config.js snippet is not being injected there, these chunks of code can not call the Sentry API out of the box.
This can be fixed by a separate call to Sentry.init, but IMHO it would be better if the sentry.server.config.js would also be injected there.
Apart from that everything that I have written about API routes also applies to SSR functions on Netlify, because the AWS lambda compatibility layer that is being used is identical.
My Workaround
Unfortunately I can't just switch to Vercel, so I had to somehow get it to work on Netlify. I think I found a "dirty workaround" and I would be really glad if someone could provide some feedback, so I know whether it contains some major flaws that need to be fixed. After all I want to catch and handle the errors I already have and not create new ones.
1. Clear sentry.server.config.js
It doesn't fully work with Netlify, so I had to remove it. However, the Sentry webpack plugin complains if this file is missing, so I had to keep it, but I removed all content from it.
2. Write some code which catches errors and sends them to Sentry
The following code is based on https://github.com/getsentry/sentry-javascript/blob/6.11.0/packages/nextjs/src/utils/withSentry.ts but without relying on ServerResponse.end. I assume this code has some major issues, because otherwise ServerResponse.end wouldn't be used by @sentry/nextjs.
Also I removed all the tracing support, because all I need is error tracking.
import * as Sentry from '@sentry/nextjs';
import * as SentryUtils from '@sentry/utils';
import * as domain from 'domain';
import type { GetServerSideProps, NextApiHandler, NextApiRequest, NextApiResponse } from 'next';
import type { IncomingMessage, ServerResponse } from 'http';
// this file is not included on the client side, so we can do the server side Sentr initialization here
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
});
// a wrapper for getServerSideProps which uses Sentry to track errors
export function withSentrySSR(getServerSideProps: GetServerSideProps): GetServerSideProps {
return async ctx => {
return runWithSentry(ctx.req, ctx.res, async () => {
return await getServerSideProps(ctx);
});
};
}
// a wrapper for API route handlers which uses Sentry to track errors
export function withSentry(handler: NextApiHandler): NextApiHandler {
return async (req, res) => {
return runWithSentry(req, res, async () => {
return await handler(req, res);
});
};
}
async function runWithSentry<R>(
req: IncomingMessage | NextApiRequest,
res: ServerResponse | NextApiResponse,
handler: () => Promise<R>,
): Promise<R> {
const local = domain.create();
local.add(req);
local.add(res);
const boundHandler = local.bind(async () => {
const currentScope = Sentry.getCurrentHub().getScope();
if (currentScope) {
currentScope.addEventProcessor(event => Sentry.Handlers.parseRequest(event, req));
}
try {
return await handler();
} catch (err) {
if (currentScope) {
currentScope.addEventProcessor(event => {
SentryUtils.addExceptionMechanism(event, {
handled: false,
});
return event;
});
Sentry.captureException(err);
}
throw err;
}
});
try {
return await boundHandler();
} finally {
try {
await Sentry.flush(2000);
} catch (err) {
console.warn(`Error while flushing Sentry events`, err);
}
}
}Using this code I am now able to track errors which are thrown from API routes
import type { NextApiHandler } from 'next';
import { withSentry } from '../../sentry/withSentry';
const handler: NextApiHandler = async () => {
throw new Error('API route failed');
};
export default withSentry(handler);as well as errors that are thrown from getServerSideProps
import { withSentrySSR } from '../sentry/withSentry';
export const getServerSideProps = withSentrySSR(async () => {
throw new Error('getServerSideProps failed');
});