Skip to content

Commit 82a0e96

Browse files
feat: Improve server error logging (#13990)
* Sanitize server stack trace on errors * use findLastIndex * add preceding newline * only clean up stack trace in dev, since in prod it's a no-op. make paths project-relative, and use as the boundary * changeset --------- Co-authored-by: Rich Harris <[email protected]>
1 parent bfa3b9c commit 82a0e96

File tree

7 files changed

+80
-3
lines changed

7 files changed

+80
-3
lines changed

.changeset/lucky-sites-wash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': minor
3+
---
4+
5+
feat: better server-side error logging

packages/kit/src/runtime/server/index.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { set_private_env, set_public_env } from '../shared-server.js';
33
import { options, get_hooks } from '__SERVER__/internal.js';
44
import { DEV } from 'esm-env';
55
import { filter_env } from '../../utils/env.js';
6+
import { format_server_error } from './utils.js';
67
import { set_read_implementation, set_manifest } from '__sveltekit/server';
78
import { set_app } from './app.js';
89

@@ -87,8 +88,14 @@ export class Server {
8788
handle: module.handle || (({ event, resolve }) => resolve(event)),
8889
handleError:
8990
module.handleError ||
90-
(({ status, error }) =>
91-
console.error((status === 404 && /** @type {Error} */ (error)?.message) || error)),
91+
(({ status, error, event }) => {
92+
const error_message = format_server_error(
93+
status,
94+
/** @type {Error} */ (error),
95+
event
96+
);
97+
console.error(error_message);
98+
}),
9299
handleFetch: module.handleFetch || (({ request, fetch }) => fetch(request)),
93100
handleValidationError:
94101
module.handleValidationError ||

packages/kit/src/runtime/server/utils.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,59 @@ export function has_prerendered_path(manifest, pathname) {
187187
);
188188
}
189189

190+
/**
191+
* Formats the error into a nice message with sanitized stack trace
192+
* @param {number} status
193+
* @param {Error} error
194+
* @param {import('@sveltejs/kit').RequestEvent} event
195+
*/
196+
export function format_server_error(status, error, event) {
197+
const formatted_text = `\n\x1b[1;31m[${status}] ${event.request.method} ${event.url.pathname}\x1b[0m\n`;
198+
199+
if (status === 404) {
200+
return formatted_text + error.message;
201+
}
202+
203+
return formatted_text + (DEV ? clean_up_stack_trace(error) : error.stack);
204+
}
205+
206+
/**
207+
* In dev, tidy up stack traces by making paths relative to the current project directory
208+
* @param {string} file
209+
*/
210+
let relative = (file) => file;
211+
212+
if (DEV) {
213+
try {
214+
const path = await import('node:path');
215+
const process = await import('node:process');
216+
217+
relative = (file) => path.relative(process.cwd(), file);
218+
} catch {
219+
// do nothing
220+
}
221+
}
222+
223+
/**
224+
* Provides a refined stack trace by excluding lines following the last occurrence of a line containing +page. +layout. or +server.
225+
* @param {Error} error
226+
*/
227+
export function clean_up_stack_trace(error) {
228+
const stack_trace = (error.stack?.split('\n') ?? []).map((line) => {
229+
return line.replace(/\((.+)(:\d+:\d+)\)$/, (_, file, loc) => `(${relative(file)}${loc})`);
230+
});
231+
232+
// progressive enhancement for people who haven't configured kit.files.src to something else
233+
const last_line_from_src_code = stack_trace.findLastIndex((line) => /\(src[\\/]/.test(line));
234+
235+
if (last_line_from_src_code === -1) {
236+
// default to the whole stack trace
237+
return error.stack;
238+
}
239+
240+
return stack_trace.slice(0, last_line_from_src_code + 1).join('\n');
241+
}
242+
190243
/**
191244
* Returns the filename without the extension. e.g., `+page.server`, `+page`, etc.
192245
* @param {string | undefined} node_id

packages/kit/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"checkJs": true,
55
"noEmit": true,
66
"strict": true,
7-
"target": "es2022",
7+
"target": "es2023",
88
"module": "node16",
99
"moduleResolution": "node16",
1010
"allowSyntheticDefaultImports": true,

playgrounds/basic/src/routes/+page.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
<h1>Welcome to SvelteKit</h1>
88

9+
<p><a href="images">images</a></p>
10+
<p><a href="force-error">force error</a></p>
11+
912
<h2>Todos</h2>
1013

1114
<h3>todo via JS</h3>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const actions = {
2+
default: async () => {
3+
throw new Error('test');
4+
}
5+
};
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<br />
2+
<form method="post">
3+
<button type="submit"> Trigger server error </button>
4+
</form>

0 commit comments

Comments
 (0)