kwhitley / itty-router-extras

An assortment of delicious extras for the calorie-light itty-router.
MIT License
84 stars 12 forks source link

Log Errors Using Router? #4

Closed antholeole closed 3 years ago

antholeole commented 3 years ago

Hi there! I'm a fan of itty & itty extras. Unfortunately, I can't seem to find a way to log more details about 500 errors - errors that happen somewhere upstream in a router that throws. Generally, this works when I'm not using a router to log errors:

addEventListener('fetch', async (event) => {
  event.respondWith((router.handle(event.request, event) as Promise<Response>).catch((e) => {
    console.log(e)
    console.log(e.message)
    console.log(e.name)
    throw e
  }))
})

but unfortunately, the errors do not get logged - with throwable router, it seems that the errors get swallowed and returned without hitting the catch block of the promise.

My purposed solution would just be, instead of swallowing (try {...} on error { return 500 }) why not something that looks like:

try {
...
} catch (e) {
if (error is StatusError) {
 ...(handle)
else {
throw e
}
}

this will allow developers to debug the errors that they didn't intentionally throw - i.e. only StatusError and friends get handled, but actual developer errors can bubble up and be logged accordingly.

Thanks!

kwhitley commented 3 years ago

Hey @antholeole, glad you like the new extras! I'll be able to dive in an replicate what you’re talking about in a couple hours, but at a glance, it looks like the prob is you trying to catch with ThrowableRouter, which already catches (if you want to catch the handle yourself, just use Router from core itty). That said, a couple things on this... first of all, I’m right there with you, and in my own codebase (slick.af) I expose the stack through the error. Gotta warn you though, Worker errors (stack in particular) are often pretty useless in my experience, as everything was minified and such. I ripped it out as a quick first pass of the extras, assuming most devs wouldn't want to expose the stack trace by default, but I'll add an option in. Worst case, later tonight, I'll give you a workaround for the meantime!

antholeole commented 3 years ago

I actually had something very similar to ThrowableRouter before I found this package, but decided to flip to the package because I wanted less code to maintain + the extra features were very nice.

I added a proposed solution to the original issue. That proposal is essentially what I was doing before I was using ThrowableRouter.

kwhitley commented 3 years ago

So I'm working on this right now (and adding test coverage), and I have a couple notes on it:

Thoughts?

kwhitley commented 3 years ago

Also, since you use both (itty and extras), I'd love your opinion on something... I've obviously been using both throughout my own code, but the README examples on itty core don't currently use extras (they weren't published, plus I wanted to make it readable for people that only wanted the core router for whatever reason). Think I should include extras in the examples?

That's obviously when the code gets really tiny...

antholeole commented 3 years ago

I like your solution, but what happens in the case that a user wants to use something like sentry for remote error logging? In this case, using a throwable router is impossible - no exceptions will ever make it up to the sentry catcher.

Regardless, thanks for your work on this package - the utils are great and with or without Throwable Router and I'll definitely continue to use it.

Per your question - I think how you have it now is nice, with a callout to itty-router-extras in the itty-router README. Helps with discoverability.

When I look at a readme I usually laser lock on my use-case (command f what I need) and if an example pops up that uses another package, I may try to copy and paste that directly into my code and wonder why something like StatusError isn't importing.

So my vote goes no - don't include the itty-router-extras examples on itty-core as I think your callout in the itty-router to itty-router-extras does the job.

kwhitley commented 3 years ago

Surely something like this would work?

without async event.waitUntil after response (easier)

import { Router } from 'itty-router'
import { error } from 'itty-router-extras'
import { Sentry } from 'borderless/worker-sentry'

const sentry = new Sentry({ dsn: 'https://123@456.ingest.sentry.io/789' })
const router = Router() // not ThrowableRouter which will respond automatically to throws

addEventListener('fetch', event => {
  event.respondWith(
    router
      // attempt to handle route
      .handle(event.request, event)

      // return/respond to request with sentry response promise
      .catch(err => sentry.captureException(err, {
        tags: {},
        user: {
          ip_address: event.request.headers.get("cf-connecting-ip"),
        },
      })

      // respond with final catch-all in case sentry logging throws somehow
      .catch(err => error(500, { 
        error: 'Could not log error to Sentry'
        details: err.message,
        stack: err.stack,
      })
  )
})

or if you want to fire of the logger after the request resolves to speed up response times (what they attempt to show in their docs I believe), just replace the sentry block with this:

.catch(err => {
  // fire off the logger promise
  event.waitUntil(sentry.captureException(err, {
    tags: {},
    user: {
      ip_address: event.request.headers.get("cf-connecting-ip"),
    }
  })

  // then respond to the actual request immediately
  return error(500, 'Logging error to sentry')
})
antholeole commented 3 years ago

Yep! Those would work perfectly fine! I'm just saying that it has incompatibility with the ThrowableRouter. That being said, I know the compatibility with bubble-up errors isn't what you're going for with ThrowableRouter - I'll take your advice and switch to the core router with custom error handling.

Thanks for all the help! Feel free to close the issue; I'll leave it open just in case you want to make changes based on it and want to create a PR.

kwhitley commented 3 years ago

Ok cool, we'll I’m def expanding the extras API a bit to have more flexible errors (per the convo above), but yeah... ThrowableRouter is the broad stroke easy path for people with nothing elaborate in their flow. I want to get loads of people into the Workers ecosystem so we can see crazy cool stuff, so I’m just trying to make things look easy as a starting point.

And then for my rapid dev projects/experiments, I never bother with incredibly useful things like error logging... because you know, time and such ;) I'll close this once the update is released to remind me that I've unfinished business to attend to!

Thanks for the feedback/discussion!