getsentry / sentry-javascript

Official Sentry SDKs for JavaScript
https://sentry.io
MIT License
7.95k stars 1.57k forks source link

POST request handled as GET in nextjs route handlers #13908

Open rodolfoBee opened 2 weeks ago

rodolfoBee commented 2 weeks ago

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/nextjs

SDK Version

8.33.1

Framework Version

No response

Link to Sentry event

https://dev-curumas.sentry.io/issues/5968125190/?project=5591101&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D&referrer=issue-stream&statsPeriod=7d&stream_index=0

Reproduction Example/SDK Setup

No response

Steps to Reproduce

Repro: next-test-sentry-main.zip Follow the steps in the readme:

  1. npm install
  2. update DSN in the sentry config files
  3. npm run dev
  4. send a POST request to http://localhost:3000/api/test

Expected Result

The event in sentry has the POST request information including body.

Actual Result

The request in the Sentry event is treated as a GET request, even though the event is associated with the transaction POST /api/test (http.server)

Image

lforst commented 2 weeks ago

Thanks for raising. For the next person reading this. We need to start filling the request interface properly: https://develop.sentry.dev/sdk/event-payloads/request/

haveneersrobin commented 2 weeks ago

I have worked around this issue in our code by adding an event processor which gets a clone of the request so .json() can be called once more. This can fail if there is no body, so that is something to keep in mind. Roughly looks like this, but I can imagine there is a better way 😄

type EventProcessorCreator = (
  httpRequest: NextRequest,
  status_code: number,
) => Promise<(event: Sentry.Event, hint: Sentry.EventHint) => Promise<Sentry.Event>>;

export const getRequestEventProcessor: EventProcessorCreator =
  async (httpRequest, status_code) => async (event, hint) => {
    const { request, contexts, breadcrumbs } = event;
    const {
      url,
      method,
      cookies,
      nextUrl: { searchParams, pathname },
    } = httpRequest;

    return {
      ...event,
      request: {
        ...request,
        url,
        query_string: Object.fromEntries(searchParams),
        method: method,
        data: await httpRequest.json(),
        cookies: Object.fromEntries(cookies.getAll().map(({ name, value }) => [name, value])),
      },
      contexts: {
        ...contexts,
        response: {
          ...(contexts?.response && { ...contexts.response }),
          status_code,
        },
      },
      transaction: `${method} ${pathname}`,
      breadcrumbs: breadcrumbs?.map(value => ({
        ...value,
        message: value.message ? stripAnsi(value.message) : '',
      })),
    };
  };

// ...

const eventProcessor = await getRequestEventProcessor(request, status);
Sentry.withScope(scope => {
    // ...
    scope.addEventProcessor(eventProcessor);
  });

Setting event.contexts.response directly gave TS errors

malay44 commented 1 week ago

I attempted to reproduce the issue. Although the error was logged on the dashboard, I was unable to view the request section. You can check out the event details here.

To investigate further, I captured the event object being sent from the Next.js server to Sentry using the beforeSend function:

import * as Sentry from "@sentry/nextjs";
import { writeFile } from "fs";

Sentry.init({
  dsn: "<DNS>",
  tracesSampleRate: 1,
  debug: false,
  beforeSend(...args) {
    writeFile("sentryEvent.json", JSON.stringify(args), () => {});
    return args[0];
  },
});

You can find the sentry-event.json file here. However, this event doesn't seem to include the request object.

lforst commented 1 week ago

@malay44 we can repro the issue. We just need to fix it. It's medium prio for us atm.