@@ -18,6 +18,7 @@ import {
1818} from "../personalAccessToken.server" ;
1919import { assertTokenOrganizationClaim , assertUserActorScope } from "../userActorEnvironment.server" ;
2020import { safeJsonParse } from "~/utils/json" ;
21+ import { sanitizeHttpUrl } from "~/utils/sanitizeHttpUrl" ;
2122import type { AuthenticatedWorkerInstance } from "~/v3/services/worker/workerGroupTokenService.server" ;
2223import { WorkerGroupTokenService } from "~/v3/services/worker/workerGroupTokenService.server" ;
2324import type { API_VERSIONS } from "~/api/versions" ;
@@ -31,24 +32,70 @@ import { tenantContext, tenantContextFromAuthEnvironment } from "~/services/tena
3132// Client aborts and service-level validation errors aren't bugs — they're
3233// expected at API boundaries. Log them at `warn` so they stay in stdout
3334// without flowing to Sentry via Logger.onError.
35+ type DrizzleQueryError = Error & {
36+ query : string ;
37+ params : unknown [ ] ;
38+ cause ?: unknown ;
39+ } ;
40+
41+ function isDrizzleQueryError ( error : unknown ) : error is DrizzleQueryError {
42+ if ( ! ( error instanceof Error ) ) return false ;
43+
44+ try {
45+ return (
46+ error . message . startsWith ( "Failed query:" ) &&
47+ typeof ( error as Partial < DrizzleQueryError > ) . query === "string" &&
48+ Array . isArray ( ( error as Partial < DrizzleQueryError > ) . params )
49+ ) ;
50+ } catch {
51+ return false ;
52+ }
53+ }
54+
55+ function safeErrorCode ( error : unknown ) : string | number | boolean | undefined {
56+ if ( ( typeof error !== "object" && typeof error !== "function" ) || error === null ) return ;
57+
58+ try {
59+ const code = ( error as { code ?: unknown } ) . code ;
60+ return typeof code === "string" || typeof code === "number" || typeof code === "boolean"
61+ ? code
62+ : undefined ;
63+ } catch {
64+ return ;
65+ }
66+ }
67+
68+ export function boundaryErrorLogValue ( error : unknown ) {
69+ if ( isDrizzleQueryError ( error ) ) {
70+ const code = safeErrorCode ( error . cause ) ;
71+ return {
72+ name : "DrizzleQueryError" ,
73+ message : "Database query failed" ,
74+ ...( code === undefined ? { } : { causeCode : code } ) ,
75+ } ;
76+ }
77+
78+ return error instanceof Error
79+ ? { name : error . name , message : error . message , stack : error . stack }
80+ : String ( error ) ;
81+ }
82+
3483function logBoundaryError (
3584 message : "Error in loader" | "Error in action" | "Unroutable id" ,
3685 error : unknown ,
3786 url : string
3887) {
39- const formatted =
40- error instanceof Error
41- ? { name : error . name , message : error . message , stack : error . stack }
42- : String ( error ) ;
88+ const formatted = boundaryErrorLogValue ( error ) ;
89+ const sanitizedUrl = sanitizeHttpUrl ( url ) ;
4390 const isExpected =
4491 error instanceof Error &&
4592 ( error . name === "AbortError" ||
4693 error instanceof ServiceValidationError ||
4794 error instanceof EngineServiceValidationError ) ;
4895 if ( isExpected ) {
49- logger . warn ( message , { error : formatted , url } ) ;
96+ logger . warn ( message , { error : formatted , url : sanitizedUrl } ) ;
5097 } else {
51- logger . error ( message , { error : formatted , url } ) ;
98+ logger . error ( message , { error : formatted , url : sanitizedUrl } ) ;
5299 }
53100}
54101
@@ -466,7 +513,10 @@ export function createLoaderApiRoute<
466513 corsStrategy !== "none"
467514 ) ;
468515 } catch ( innerError ) {
469- logger . error ( "[apiBuilder] Failed to handle error" , { error, innerError } ) ;
516+ logger . error ( "[apiBuilder] Failed to handle error" , {
517+ error : boundaryErrorLogValue ( error ) ,
518+ innerError : boundaryErrorLogValue ( innerError ) ,
519+ } ) ;
470520
471521 return json ( { error : "Internal Server Error" } , { status : 500 } ) ;
472522 }
@@ -769,7 +819,10 @@ export function createLoaderPATApiRoute<
769819 corsStrategy !== "none"
770820 ) ;
771821 } catch ( innerError ) {
772- logger . error ( "[apiBuilder] Failed to handle error" , { error, innerError } ) ;
822+ logger . error ( "[apiBuilder] Failed to handle error" , {
823+ error : boundaryErrorLogValue ( error ) ,
824+ innerError : boundaryErrorLogValue ( innerError ) ,
825+ } ) ;
773826
774827 return json ( { error : "Internal Server Error" } , { status : 500 } ) ;
775828 }
@@ -1061,7 +1114,10 @@ export function createActionPATApiRoute<
10611114 corsStrategy !== "none"
10621115 ) ;
10631116 } catch ( innerError ) {
1064- logger . error ( "[apiBuilder] Failed to handle error" , { error, innerError } ) ;
1117+ logger . error ( "[apiBuilder] Failed to handle error" , {
1118+ error : boundaryErrorLogValue ( error ) ,
1119+ innerError : boundaryErrorLogValue ( innerError ) ,
1120+ } ) ;
10651121
10661122 return json ( { error : "Internal Server Error" } , { status : 500 } ) ;
10671123 }
@@ -1407,7 +1463,10 @@ export function createActionApiRoute<
14071463 corsStrategy !== "none"
14081464 ) ;
14091465 } catch ( innerError ) {
1410- logger . error ( "[apiBuilder] Failed to handle error" , { error, innerError } ) ;
1466+ logger . error ( "[apiBuilder] Failed to handle error" , {
1467+ error : boundaryErrorLogValue ( error ) ,
1468+ innerError : boundaryErrorLogValue ( innerError ) ,
1469+ } ) ;
14111470
14121471 return json ( { error : "Internal Server Error" } , { status : 500 } ) ;
14131472 }
@@ -1679,7 +1738,10 @@ export function createMultiMethodApiRoute<
16791738 corsStrategy !== "none"
16801739 ) ;
16811740 } catch ( innerError ) {
1682- logger . error ( "[apiBuilder] Failed to handle error" , { error, innerError } ) ;
1741+ logger . error ( "[apiBuilder] Failed to handle error" , {
1742+ error : boundaryErrorLogValue ( error ) ,
1743+ innerError : boundaryErrorLogValue ( innerError ) ,
1744+ } ) ;
16831745 return json ( { error : "Internal Server Error" } , { status : 500 } ) ;
16841746 }
16851747 }
0 commit comments