@@ -28,7 +28,11 @@ import { emptyReadableStream, toReadableStream } from "utils/stream.js";
2828import type { OpenNextHandlerOptions } from "types/overrides.js" ;
2929import { createGenericHandler } from "../core/createGenericHandler.js" ;
3030import { resolveImageLoader } from "../core/resolve.js" ;
31- import { IgnorableError } from "../utils/error.js" ;
31+ import {
32+ FatalError ,
33+ IgnorableError ,
34+ RecoverableError ,
35+ } from "../utils/error.js" ;
3236import { debug , error } from "./logger.js" ;
3337import { optimizeImage } from "./plugins/image-optimization/image-optimization.js" ;
3438import { setNodeEnv } from "./util.js" ;
@@ -83,10 +87,8 @@ export async function defaultHandler(
8387 // We return a 400 here if imageParams returns an errorMessage
8488 // https://github.com/vercel/next.js/blob/512d8283054407ab92b2583ecce3b253c3be7b85/packages/next/src/server/next-server.ts#L937-L941
8589 if ( "errorMessage" in imageParams ) {
86- error (
87- "Error during validation of image params" ,
88- imageParams . errorMessage ,
89- ) ;
90+ const clientError = new RecoverableError ( imageParams . errorMessage , 400 ) ;
91+ error ( "Error during validation of image params" , clientError ) ;
9092 return buildFailureResponse (
9193 imageParams . errorMessage ,
9294 options ?. streamCreator ,
@@ -115,56 +117,17 @@ export async function defaultHandler(
115117 ) ;
116118 return buildSuccessResponse ( result , options ?. streamCreator , etag ) ;
117119 } catch ( e : any ) {
118- // Determine if this is a client error (4xx) or server error (5xx)
119- let statusCode = 500 ; // Default to 500 for unknown errors
120- const isClientError =
121- e &&
122- typeof e === "object" &&
123- ( ( "statusCode" in e &&
124- typeof e . statusCode === "number" &&
125- e . statusCode >= 400 &&
126- e . statusCode < 500 ) ||
127- e . code === "ENOTFOUND" ||
128- e . code === "ECONNREFUSED" ||
129- ( e . message &&
130- ( e . message . includes ( "403" ) ||
131- e . message . includes ( "404" ) ||
132- e . message . includes ( "Access Denied" ) ||
133- e . message . includes ( "Not Found" ) ) ) ) ;
120+ // Determine if this is a client error (4xx) and convert it to appropriate error type
121+ const classifiedError = classifyError ( e ) ;
134122
135- // Only log actual server errors as errors, log client errors as debug
136- if ( isClientError ) {
137- debug ( "Client error in image optimization" , e ) ;
138- } else {
139- error ( "Failed to optimize image" , e ) ;
140- }
123+ // Log with the appropriate level based on the error type
124+ error ( "Image optimization error" , classifiedError ) ;
141125
142- // Determine appropriate status code based on error type
143- if ( e && typeof e === "object" ) {
144- if ( "statusCode" in e && typeof e . statusCode === "number" ) {
145- statusCode = e . statusCode ;
146- } else if ( "code" in e ) {
147- const code = e . code as string ;
148- if ( code === "ENOTFOUND" || code === "ECONNREFUSED" ) {
149- statusCode = 404 ;
150- }
151- } else if ( e . message ) {
152- if ( e . message . includes ( "403" ) || e . message . includes ( "Access Denied" ) ) {
153- statusCode = 403 ;
154- } else if (
155- e . message . includes ( "404" ) ||
156- e . message . includes ( "Not Found" )
157- ) {
158- statusCode = 404 ;
159- }
160- }
161- }
162-
163- // Pass through the original error message from Next.js
126+ // Pass through the error message from Next.js
164127 return buildFailureResponse (
165- e . message || "Internal server error" ,
128+ classifiedError . message || "Internal server error" ,
166129 options ?. streamCreator ,
167- statusCode ,
130+ classifiedError . statusCode ,
168131 ) ;
169132 }
170133}
@@ -173,6 +136,63 @@ export async function defaultHandler(
173136// Helper functions //
174137//////////////////////
175138
139+ /**
140+ * Classifies an error and converts it to the appropriate OpenNext error type
141+ * with the correct status code and logging level.
142+ */
143+ function classifyError ( e : any ) : IgnorableError | RecoverableError | FatalError {
144+ // Default values
145+ let statusCode = 500 ;
146+ const message = e ?. message || "Internal server error" ;
147+
148+ // If it's already an OpenNext error, return it directly
149+ if ( e && typeof e === "object" && "__openNextInternal" in e ) {
150+ return e ;
151+ }
152+
153+ // Determine if this is a client error (4xx) or server error (5xx)
154+ const isClientError =
155+ e &&
156+ typeof e === "object" &&
157+ ( ( "statusCode" in e &&
158+ typeof e . statusCode === "number" &&
159+ e . statusCode >= 400 &&
160+ e . statusCode < 500 ) ||
161+ e . code === "ENOTFOUND" ||
162+ e . code === "ECONNREFUSED" ||
163+ ( e . message &&
164+ ( e . message . includes ( "403" ) ||
165+ e . message . includes ( "404" ) ||
166+ e . message . includes ( "Access Denied" ) ||
167+ e . message . includes ( "Not Found" ) ) ) ) ;
168+
169+ // Determine appropriate status code based on error type
170+ if ( e && typeof e === "object" ) {
171+ if ( "statusCode" in e && typeof e . statusCode === "number" ) {
172+ statusCode = e . statusCode ;
173+ } else if ( "code" in e ) {
174+ const code = e . code as string ;
175+ if ( code === "ENOTFOUND" || code === "ECONNREFUSED" ) {
176+ statusCode = 404 ;
177+ }
178+ } else if ( e . message ) {
179+ if ( e . message . includes ( "403" ) || e . message . includes ( "Access Denied" ) ) {
180+ statusCode = 403 ;
181+ } else if ( e . message . includes ( "404" ) || e . message . includes ( "Not Found" ) ) {
182+ statusCode = 404 ;
183+ }
184+ }
185+ }
186+
187+ // Client errors (4xx) are wrapped as IgnorableError to prevent noise in monitoring
188+ if ( isClientError || statusCode < 500 ) {
189+ return new IgnorableError ( message , statusCode ) ;
190+ }
191+
192+ // Server errors (5xx) are marked as FatalError to ensure proper monitoring
193+ return new FatalError ( message , statusCode ) ;
194+ }
195+
176196function validateImageParams (
177197 headers : OutgoingHttpHeaders ,
178198 query ?: InternalEvent [ "query" ] ,
@@ -305,11 +325,24 @@ async function downloadHandler(
305325 const request = https . get ( url , ( response ) => {
306326 // Check for HTTP error status codes
307327 if ( response . statusCode && response . statusCode >= 400 ) {
308- error ( `Failed to get image: HTTP ${ response . statusCode } ` ) ;
328+ // Create an IgnorableError with appropriate status code
329+ const clientError = new IgnorableError (
330+ response . statusMessage || `HTTP error ${ response . statusCode } ` ,
331+ response . statusCode ,
332+ ) ;
333+
334+ // Log the error using proper error logger to handle it correctly
335+ error ( "Client error fetching image" , clientError , {
336+ status : response . statusCode ,
337+ statusText : response . statusMessage ,
338+ url : url . href ,
339+ } ) ;
340+
309341 res . statusCode = response . statusCode ;
310342 res . end ( ) ;
311343 return ;
312344 }
345+
313346 // IncomingMessage is a Readable stream, not a Writable
314347 // We need to pipe it directly to the response
315348 response
@@ -320,8 +353,12 @@ async function downloadHandler(
320353 }
321354 res . end ( ) ;
322355 } )
323- . once ( "error" , ( pipeErr ) => {
324- error ( "Failed to get image during piping" , pipeErr ) ;
356+ . once ( "error" , ( pipeErr : Error ) => {
357+ const clientError = new IgnorableError (
358+ `Error during image piping: ${ pipeErr . message } ` ,
359+ 400 ,
360+ ) ;
361+ error ( "Failed to get image during piping" , clientError ) ;
325362 if ( ! res . headersSent ) {
326363 res . statusCode = 400 ;
327364 }
@@ -330,24 +367,25 @@ async function downloadHandler(
330367 } ) ;
331368
332369 request . on ( "error" , ( err : Error & { code ?: string } ) => {
333- // For network errors, these are typically client errors (bad URL, etc.)
334- // so log as debug instead of error to avoid false alarms
370+ // For network errors, convert to appropriate error type based on error code
335371 const isClientError =
336372 err . code === "ENOTFOUND" || err . code === "ECONNREFUSED" ;
337-
338- if ( isClientError ) {
339- // Log the full error for debugging but don't expose it to the client
340- debug ( "Client error fetching image" , {
341- code : err . code ,
342- message : err . message ,
343- } ) ;
344- res . statusCode = 404 ; // Not Found for DNS or connection errors
345- } else {
346- error ( "Failed to get image" , err ) ;
347- res . statusCode = 400 ; // Bad Request for other errors
348- }
349-
350- // Don't send the error message back to the client
373+ const statusCode = isClientError ? 404 : 400 ;
374+
375+ // Create appropriate error type
376+ const clientError = new IgnorableError (
377+ err . message || `Error fetching image: ${ err . code || "unknown error" } ` ,
378+ statusCode ,
379+ ) ;
380+
381+ // Log with error function but it will be handled properly based on error type
382+ error ( "Error fetching image" , clientError , {
383+ code : err . code ,
384+ message : err . message ,
385+ url : url . href ,
386+ } ) ;
387+
388+ res . statusCode = statusCode ;
351389 res . end ( ) ;
352390 } ) ;
353391 }
0 commit comments