mirror of
https://github.com/withastro/astro.git
synced 2025-03-24 23:21:57 -05:00
Handle unhandledrejections in the dev server (#9424)
* Handle unhandledrejections in the dev server * Adding changeset * Update .changeset/curvy-lobsters-crash.md Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca> * Use AsyncLocalStorage * Return errorWithMetadata * Send the error to the browser --------- Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
parent
8c9fe0092a
commit
e1a5a2d36a
5 changed files with 82 additions and 26 deletions
5
.changeset/curvy-lobsters-crash.md
Normal file
5
.changeset/curvy-lobsters-crash.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Prevents dev server from crashing on unhandled rejections, and adds a helpful error message
|
|
@ -1292,3 +1292,12 @@ export const CantRenderPage = {
|
|||
|
||||
// Generic catch-all - Only use this in extreme cases, like if there was a cosmic ray bit flip
|
||||
export const UnknownError = { name: 'UnknownError', title: 'Unknown Error.' } satisfies ErrorData;
|
||||
|
||||
export const UnhandledRejection = {
|
||||
name: 'UnhandledRejection',
|
||||
title: 'Unhandled rejection',
|
||||
message: (stack: string) => {
|
||||
return `Astro detected an unhandled rejection. Here's the stack trace:\n${stack}`;
|
||||
},
|
||||
hint: 'Make sure your promises all have an `await` or a `.catch()` handler.'
|
||||
}
|
||||
|
|
33
packages/astro/src/vite-plugin-astro-server/error.ts
Normal file
33
packages/astro/src/vite-plugin-astro-server/error.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import type { ModuleLoader } from '../core/module-loader/index.js'
|
||||
import type { AstroConfig } from '../@types/astro.js';
|
||||
import type DevPipeline from './devPipeline.js';
|
||||
|
||||
import { collectErrorMetadata } from '../core/errors/dev/index.js';
|
||||
import { createSafeError } from '../core/errors/index.js';
|
||||
import { formatErrorMessage } from '../core/messages.js';
|
||||
import { eventError, telemetry } from '../events/index.js';
|
||||
|
||||
export function recordServerError(loader: ModuleLoader, config: AstroConfig, pipeline: DevPipeline, _err: unknown) {
|
||||
const err = createSafeError(_err);
|
||||
|
||||
// This could be a runtime error from Vite's SSR module, so try to fix it here
|
||||
try {
|
||||
loader.fixStacktrace(err);
|
||||
} catch {}
|
||||
|
||||
// This is our last line of defense regarding errors where we still might have some information about the request
|
||||
// Our error should already be complete, but let's try to add a bit more through some guesswork
|
||||
const errorWithMetadata = collectErrorMetadata(err, config.root);
|
||||
|
||||
telemetry.record(eventError({ cmd: 'dev', err: errorWithMetadata, isFatal: false }));
|
||||
|
||||
pipeline.logger.error(
|
||||
null,
|
||||
formatErrorMessage(errorWithMetadata, pipeline.logger.level() === 'debug')
|
||||
);
|
||||
|
||||
return {
|
||||
error: err,
|
||||
errorWithMetadata
|
||||
};
|
||||
}
|
|
@ -10,6 +10,12 @@ import { baseMiddleware } from './base.js';
|
|||
import { createController } from './controller.js';
|
||||
import DevPipeline from './devPipeline.js';
|
||||
import { handleRequest } from './request.js';
|
||||
import { AstroError, AstroErrorData } from '../core/errors/index.js';
|
||||
import { getViteErrorPayload } from '../core/errors/dev/index.js';
|
||||
import { AsyncLocalStorage } from 'node:async_hooks';
|
||||
import { IncomingMessage } from 'node:http';
|
||||
import { setRouteError } from './server-state.js';
|
||||
import { recordServerError } from './error.js';
|
||||
|
||||
export interface AstroPluginOptions {
|
||||
settings: AstroSettings;
|
||||
|
@ -30,6 +36,7 @@ export default function createVitePluginAstroServer({
|
|||
const pipeline = new DevPipeline({ logger, manifest, settings, loader });
|
||||
let manifestData: ManifestData = createRouteManifest({ settings, fsMod }, logger);
|
||||
const controller = createController({ loader });
|
||||
const localStorage = new AsyncLocalStorage();
|
||||
|
||||
/** rebuild the route cache + manifest, as needed. */
|
||||
function rebuildManifest(needsManifestRebuild: boolean) {
|
||||
|
@ -43,6 +50,22 @@ export default function createVitePluginAstroServer({
|
|||
viteServer.watcher.on('unlink', rebuildManifest.bind(null, true));
|
||||
viteServer.watcher.on('change', rebuildManifest.bind(null, false));
|
||||
|
||||
function handleUnhandledRejection(rejection: any) {
|
||||
const error = new AstroError({
|
||||
...AstroErrorData.UnhandledRejection,
|
||||
message: AstroErrorData.UnhandledRejection.message(rejection?.stack || rejection)
|
||||
});
|
||||
const store = localStorage.getStore();
|
||||
if(store instanceof IncomingMessage) {
|
||||
const request = store;
|
||||
setRouteError(controller.state, request.url!, error);
|
||||
}
|
||||
const { errorWithMetadata } = recordServerError(loader, settings.config, pipeline, error);
|
||||
setTimeout(async () => loader.webSocketSend(await getViteErrorPayload(errorWithMetadata)), 200)
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', handleUnhandledRejection);
|
||||
|
||||
return () => {
|
||||
// Push this middleware to the front of the stack so that it can intercept responses.
|
||||
// fix(#6067): always inject this to ensure zombie base handling is killed after restarts
|
||||
|
@ -57,13 +80,15 @@ export default function createVitePluginAstroServer({
|
|||
response.end();
|
||||
return;
|
||||
}
|
||||
handleRequest({
|
||||
pipeline,
|
||||
manifestData,
|
||||
controller,
|
||||
incomingRequest: request,
|
||||
incomingResponse: response,
|
||||
manifest,
|
||||
localStorage.run(request, () => {
|
||||
handleRequest({
|
||||
pipeline,
|
||||
manifestData,
|
||||
controller,
|
||||
incomingRequest: request,
|
||||
incomingResponse: response,
|
||||
manifest,
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ import { runWithErrorHandling } from './controller.js';
|
|||
import type DevPipeline from './devPipeline.js';
|
||||
import { handle500Response } from './response.js';
|
||||
import { handleRoute, matchRoute } from './route.js';
|
||||
import { recordServerError } from './error.js';
|
||||
|
||||
type HandleRequest = {
|
||||
pipeline: DevPipeline;
|
||||
|
@ -89,26 +90,9 @@ export async function handleRequest({
|
|||
});
|
||||
},
|
||||
onError(_err) {
|
||||
const err = createSafeError(_err);
|
||||
|
||||
// This could be a runtime error from Vite's SSR module, so try to fix it here
|
||||
try {
|
||||
moduleLoader.fixStacktrace(err);
|
||||
} catch {}
|
||||
|
||||
// This is our last line of defense regarding errors where we still might have some information about the request
|
||||
// Our error should already be complete, but let's try to add a bit more through some guesswork
|
||||
const errorWithMetadata = collectErrorMetadata(err, config.root);
|
||||
|
||||
telemetry.record(eventError({ cmd: 'dev', err: errorWithMetadata, isFatal: false }));
|
||||
|
||||
pipeline.logger.error(
|
||||
null,
|
||||
formatErrorMessage(errorWithMetadata, pipeline.logger.level() === 'debug')
|
||||
);
|
||||
const { error, errorWithMetadata } = recordServerError(moduleLoader, config, pipeline, _err);
|
||||
handle500Response(moduleLoader, incomingResponse, errorWithMetadata);
|
||||
|
||||
return err;
|
||||
return error;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue