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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Edited following discussion in #6499 (comment)
SvelteKit handles errors very inconsistently. If you throw an
Errorin aloadfunction,$page.errorwill be that very sameErrorobject 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,messageand (in dev)stackplus whatever additional properties happened to be lying around on the original error (e.g.code,frameetc) while expected errors (the kind thrown withthrow error(status, message)havestatus(redundantly),messageand__is_http_error(shudder).Everything would get so much simpler (both implementation-wise and for users) if we eliminated all this ambiguity:
handleErrorreturns 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.sveltepage or include it in a JSON response, especially when doing so creates all the confusion described above.handleErrorcould be given the responsibility of creating a JSON-friendly non-sensitive-data-containing object representing the error. It would default to this, if nohandleFilewas specified or noreturnvalue was provided......but could include additional metadata:
Make
$page.errorthat same POJOInstead of playing 'is it an
ErrororHttpErroror a POJO?', we can make$page.errorjust be a POJO:For unexpected errors, we default to replacing
messagewith '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 acodethat can be used for i18n:Simpler JSON representation
If you hit a route with
fetch, the response should just be what was returned fromhandleErroror thrown withthrow error(...):Type safety
We could type
handleErrorand$page.errorby defining anApp.PageErrorinterface, as we do withApp.Localsand so on:One open question is whether
message: stringwould always be a required property. I think it probably needs to be, otherwise the default behaviour ofhandleErrorwould 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_FOUNDone above,$page.errorwould just be that object:In the case of _un_expected errors,
$page.errorwould 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