unjs / nitro

Next Generation Server Toolkit. Create web servers with everything you need and deploy them wherever you prefer.
https://nitro.unjs.io
MIT License
5.83k stars 492 forks source link

Ability to see where an error was thrown #1363

Open fmoessle opened 1 year ago

fmoessle commented 1 year ago

Describe the feature

Reproduction: https://stackblitz.com/edit/github-s5xq85?file=routes%2Findex

Hi 👋,

currently when throwing an error e.g. in a eventHandler (see reproduction) I get the following output in my console:

[nitro] [request error] [unhandled] This is an error and I want to know in which file it was thrown.
  at eval (./.nitro/dev/index.mjs:645:9)  
  at eval (./node_modules/h3/dist/index.mjs:1330:47)  
  at async Object.eval [as handler] (./node_modules/h3/dist/index.mjs:1386:19)  
  at async Server.toNodeHandle (./node_modules/h3/dist/index.mjs:1461:7)

I don't have the ability to see that the error was thrown in routes/index.ts. With my brief knowledge, I assume this has something to do with sourcemaps?

Additional information

fmoessle commented 1 year ago

Does anyone share the same issue? It's really hard to debug when the only information you get is that the error was thrown in e.g. ./.nitro/dev/index.mjs:645:9.

Maybe someone can guide me in the right direction. Happy to contribute!

pi0 commented 1 year ago

Hi dear @fmoessle I think it is a nice idea to have request info in error logs.

You can add them in src/runtime/error.ts like this:

image

We might also add same information to the HTML and JSON pages. Feel free to discover more before making PR.

In order to try locally, after cloning this repo, you can use pnpm run dev and throw an error in playground/routes/index.ts

image

Let me know if have any other questions 👍🏼

fmoessle commented 1 year ago

Thx @pi0 !

Here is an initial draft (currently a custom error handler) that is capable of telling me, where the error originated from. This is already pretty helpful.

// error.ts

import { SourceMapConsumer } from 'source-map'
import { readFile } from 'fs/promises'

export default async function (error, event) {
  // This will not work when implemented in nitro itself
  const sourceMap = await readFile('.nitro/dev/index.mjs.map', 'utf8')

  const stackTraceLine = error.stack as string
  const stackLineMatch = stackTraceLine.match(/at (.*):(\d+):(\d+)/)

  const map = await new SourceMapConsumer(sourceMap)

  if (stackLineMatch) {
    const [, filePath, line, column] = stackLineMatch
    const originalPosition = map.originalPositionFor({
      line: parseInt(line),
      column: parseInt(column),
    })

    const originalErrorPosition = `${originalPosition.source}:${originalPosition.line}:${originalPosition.column}`

    console.log(`${error.message}: ${originalErrorPosition}`)
    event.res.end(`${error.message}: ${originalErrorPosition}`)
  }
}
// nitro.config.ts

import { defineNitroConfig } from 'nitropack/config'
import errorHandler from './error'

export default defineNitroConfig({
  devErrorHandler: errorHandler,
  errorHandler: '~/error',
})

Result 🥳:

/Users/florian/nitro-sourcemap/routes/index.ts:5:0

This is far from perfect but demonstrates how it could be implemented. Could you please provide me with some guidance on how to resolve the ./nitro/dev/index.mjs.map file in src/runtime/error.ts?

fmoessle commented 1 year ago

Digging a bit deeper into sourcemaps I figured out that running

NODE_OPTIONS=--enable-source-maps pnpx nitropack dev

when starting nitro in dev mode produces a similar outcome and give me the line the code breaks without a custom error handler.

I would still prefer nitro handling the location of the error via the standard nitro error handler.

Hebilicious commented 1 year ago

Digging a bit deeper into sourcemaps I figured out that running

NODE_OPTIONS=--enable-source-maps pnpx nitropack dev

when starting nitro in dev mode produces a similar outcome and give me the line the code breaks without a custom error handler.

I would still prefer nitro handling the location of the error via the standard nitro error handler.

@pi0 Could we turn this on by default ?