getsentry / sentry-javascript

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

Sentry.wrapHandler breaks streaming Lambda handlers #12815

Open zmillman opened 4 months ago

zmillman commented 4 months ago

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/aws-serverless

SDK Version

8.15.0

Framework Version

No response

Link to Sentry event

No response

SDK Setup/Reproduction Example

Sentry.init({ dsn: process.env.SENTRY_DSN });

Steps to Reproduce

  1. Define an AWS Lambda running with the Node 20.x runtime & configure the SENTRY_DSN env variable:
import * as Sentry from "@sentry/aws-serverless";

Sentry.init({ dsn: process.env.SENTRY_DSN });

// awslambda.streamifyResponse is patched into the global object by the lambda runtime
// https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html
export const handler = Sentry.wrapHandler(
  awslambda.streamifyResponse(async function (event, responseStream, _context) {

    responseStream.write("hello");
    responseStream.end();
  })
);
  1. Make a request (any payload)
  2. Check Lambda logs for error

Expected Result

Code should execute identically as if there had been no Sentry.wrapHandler (returning "hello")

Actual Result

Error logs from code above:

2024-07-09T00:38:31.701Z    a203c0fb-7d33-4a3f-b5af-f76ed67423e6    ERROR   Invoke Error    
{
    "errorType": "TypeError",
    "errorMessage": "responseStream.write is not a function",
    "stack": [
        "TypeError: responseStream.write is not a function",
        "    at file:///var/task/index.js:299031:20",
        "    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)",
        "    at async processResult (file:///var/task/index.js:10:14)"
    ]
}

My guess is that wrapHandler wasn't written to detect the streaming Lambda handler which takes different arguments than the default handler.

The Sentry docs have this snippet:

exports.handler = Sentry.wrapHandler(async (event, context) => {
  // Your handler code
});

...but the AWS streaming handler expects the second argument to be responseStream (not context)

chargome commented 4 months ago

Hey @zmillman, thanks for reaching out. Currently our wrapHandler function does not support streamifyResponse. If you still want to use wrapHandler make sure you pass a function that takes the correct arguments.

We will put this issue onto our backlog to support streamifyResponse.

JQuezada0 commented 3 months ago

Ran into this as well, although the error message was different

underlyingStream.setContentType is not a function

Here's an example workaround that worked for me.

export const handler = awslambda.streamifyResponse(
  async function (event, responseStream, context) {
    const sentryHandler = SentryServerless.wrapHandler(async function () {
      return sstHandlerModule.handler(event, responseStream, context);
    });

    return sentryHandler(event, context, () => undefined);
  },
);

You can "trick" the wrapHandler function and then pass the actual 3 arguments needed for streaming inside.

Lms24 commented 3 months ago

Hey @JQuezada0 thanks for posting this workaround. For the time being, this is how we recommend using the wrapper with streamifyResponse.

Note: If anyone coming across this issue is using our AWS Lambda layer and relied on its auto-wrapping capabilities, I suggest switching to the alternative layer usage which gives you the freedom to apply our wrapper yourself as shown in the code snippet above.