Simpler, more consistent error handling #6499
Replies: 4 comments 27 replies
-
I agree that having I used to use Play Framework a lot and one nice feature that had was a randomly generated error ID. You could include it in your error page, which was nice because support requests would often have details like "I keep getting error pages. I just got it with error ae02ef", which we could search the logs for and find rather easily.
|
Beta Was this translation helpful? Give feedback.
-
Returning just one string as an error is not really usable for us. We have fairly complex error handling and need more information than just an error message. More precisely, an error message is the only thing, we do NOT need. Because we do client-side localization, we return an error ID. For deeper debugging, we also need to return a Trace ID. We show this Trace ID in the UI in several cases (especially unexcepted errors), so that an end user can send us this trace id, and we can find the exact log lines of this call. |
Beta Was this translation helpful? Give feedback.
-
I would just like to note the existence of RFC 7807 for error handling. I found implementations floating around for a number of other frameworks and it would be really nice to be able to point to an RFC for Svelte/SvelteKit error handling. https://www.rfc-editor.org/rfc/rfc7807 |
Beta Was this translation helpful? Give feedback.
-
One possible solution to the client-side error handling question above would be to export a <!-- src/routes/+layout.svelte -->
<script>
export function handleError({ event, error }) {
// `event` is a `LoadEvent`, containing `url`, `params`, `routeId` etc
// `error` is an unexpected error that was thrown
return {
message: `A custom message that isn't just 'Error'`,
some_other_property: 'whatever'
};
}
</script> In effect, The only wrinkle is it wouldn't be able to handle an error that occurred during the initial hydration — that would have to be replaced with a generic error. Otherwise, this seems better than a) introducing a new client-side hooks concept, or b) a lifecycle function with weird semantics |
Beta Was this translation helpful? Give feedback.
-
Edited following discussion in #6499 (comment)
SvelteKit handles errors very inconsistently. If you throw an
Error
in aload
function,$page.error
will be that very sameError
object during server-side rendering, but a POJO (created using somewhat opinionated/convoluted logic) in the client:Another inconsistency: stack traces are included during development, but not in production (because stack traces aren't the sorts of things you should generally make public). But unless you're aware of that distinction, this can just lead to unexpected behaviour.
Meanwhile, error messages (in the case where the error was unexpected, perhaps emanating from the bowels of some library you're using) are also best kept hidden from public view, yet we send them to the client.
All of this confusion extends to JSON representations of these errors. An unexpected error will be an object with a
name
,message
and (in dev)stack
plus whatever additional properties happened to be lying around on the original error (e.g.code
,frame
etc) while expected errors (the kind thrown withthrow error(status, message)
havestatus
(redundantly),message
and__is_http_error
(shudder).Everything would get so much simpler (both implementation-wise and for users) if we eliminated all this ambiguity:
handleError
returns a POJOUnexpected errors already go through
handleError
, which by default prints the error (complete with sourcemapped stacktrace) to the terminal. That seems like plenty — we don't also need to print the stack trace to the+error.svelte
page or include it in a JSON response, especially when doing so creates all the confusion described above.handleError
could be given the responsibility of creating a JSON-friendly non-sensitive-data-containing object representing the error. It would default to this, if nohandleFile
was specified or noreturn
value was provided......but could include additional metadata:
Make
$page.error
that same POJOInstead of playing 'is it an
Error
orHttpError
or a POJO?', we can make$page.error
just be a POJO:For unexpected errors, we default to replacing
message
with 'Internal Error' (not 'Internal Server Error', since the same code could throw an unexpected error when it runs on the server or in the browser). If no message is provided (e.g.throw error(404)
), we default to just 'Error'.Throw POJOs with
error(...)
When you throw an expected error with
throw error(...)
, the error doesn't go viahandleError
, because that exists primarily to help you track down unexpected errors. But you still might want to have a richer error object than just amessage
, for example containing acode
that can be used for i18n:Simpler JSON representation
If you hit a route with
fetch
, the response should just be what was returned fromhandleError
or thrown withthrow error(...)
:Type safety
We could type
handleError
and$page.error
by defining anApp.PageError
interface, as we do withApp.Locals
and so on:One open question is whether
message: string
would always be a required property. I think it probably needs to be, otherwise the default behaviour ofhandleError
would violate the type constraint. In other words, SvelteKit's types would include this declaration......and app-declared types wouldn't be able to unset it (because that's not how interface merging works).
In future: better client-side error handling
Unexpected errors that happened during client-side navigation would be printed to the console with
console.error
. Sentry and similar services can deal with this sort of thing, but it might be even nicer if we had some client-side error handling.In the case of expected client-side errors like the
NOT_FOUND
one above,$page.error
would just be that object:In the case of _un_expected errors,
$page.error
would just be a{ message: 'Error' }
object, unless we can find a good way to design a client-side version ofhandleError
. My first thought was that it could be a lifecycle function, but there really needs to be a single one that's present for the life of the app, rather than something many components could add. (Update: some thoughts on this below: #6499 (comment))Beta Was this translation helpful? Give feedback.
All reactions