diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index 738da671f8ca..ab38e5cfec3a 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -3,6 +3,7 @@ import { set_private_env, set_public_env, set_safe_public_env } from '../shared- import { options, get_hooks } from '__SERVER__/internal.js'; import { DEV } from 'esm-env'; import { filter_private_env, filter_public_env } from '../../utils/env.js'; +import { format_server_error } from './utils.js'; import { prerendering } from '__sveltekit/environment'; import { set_read_implementation, set_manifest } from '__sveltekit/server'; import { set_app } from './app.js'; @@ -77,8 +78,14 @@ export class Server { handle: module.handle || (({ event, resolve }) => resolve(event)), handleError: module.handleError || - (({ status, error }) => - console.error((status === 404 && /** @type {Error} */ (error)?.message) || error)), + (({ status, error, event }) => { + const error_message = format_server_error( + status, + /** @type {Error} */ (error), + event + ); + console.error(error_message); + }), handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)), reroute: module.reroute || (() => {}), transport: module.transport || {} diff --git a/packages/kit/src/runtime/server/utils.js b/packages/kit/src/runtime/server/utils.js index 57420e7404ce..0c758e4c5c86 100644 --- a/packages/kit/src/runtime/server/utils.js +++ b/packages/kit/src/runtime/server/utils.js @@ -180,3 +180,52 @@ export function has_prerendered_path(manifest, pathname) { (pathname.at(-1) === '/' && manifest._.prerendered_routes.has(pathname.slice(0, -1))) ); } + +/** + * Formats the error into a nice message with sanitized stack trace + * @param {number} status + * @param {Error} error + * @param {import('@sveltejs/kit').RequestEvent} event + */ +export function format_server_error(status, error, event) { + const formatted_text = `\x1b[1;31m[${status}] ${event.request.method} ${event.url.pathname}\x1b[0m\n`; + + if (status === 404) { + return formatted_text + error.message; + } + + return formatted_text + clean_up_stack_trace(error); +} + +/** + * Provides a refined stack trace by excluding lines following the last occurrence of a line containing +page. +layout. or +server. + * @param {Error} error + */ +export function clean_up_stack_trace(error) { + const stack_trace = error.stack?.split('\n') ?? []; + const last_line_from_src_code = find_last_index( + stack_trace, + (line) => + ['+page.', '+layout.', '+server.'].find((snippet) => line.includes(snippet)) !== undefined + ); + + if (last_line_from_src_code === -1) { + // default to the whole stack trace + return error.stack; + } + + return stack_trace.slice(0, last_line_from_src_code + 1).join('\n'); +} + +/** + * @param {any[]} array + * @param {(item: any, index: number, array: any[]) => boolean} predicate + */ +export function find_last_index(array, predicate) { + for (let i = array.length - 1; i >= 0; i--) { + if (predicate(array[i], i, array)) { + return i; + } + } + return -1; +} diff --git a/playgrounds/basic/src/routes/+page.svelte b/playgrounds/basic/src/routes/+page.svelte index b3872f2e7f2c..a1c819624339 100644 --- a/playgrounds/basic/src/routes/+page.svelte +++ b/playgrounds/basic/src/routes/+page.svelte @@ -12,4 +12,5 @@ diff --git a/playgrounds/basic/src/routes/force-error/+page.server.ts b/playgrounds/basic/src/routes/force-error/+page.server.ts new file mode 100644 index 000000000000..5de264103568 --- /dev/null +++ b/playgrounds/basic/src/routes/force-error/+page.server.ts @@ -0,0 +1,5 @@ +export const actions = { + default: async () => { + throw new Error('test'); + } +}; diff --git a/playgrounds/basic/src/routes/force-error/+page.svelte b/playgrounds/basic/src/routes/force-error/+page.svelte new file mode 100644 index 000000000000..96467abec651 --- /dev/null +++ b/playgrounds/basic/src/routes/force-error/+page.svelte @@ -0,0 +1,4 @@ +
+
+ +