|
1 | 1 | import { createLogger, runWithRequestContext } from '@sim/logger' |
2 | | -import { describeError, getErrorMessage, redactBoundParameters } from '@sim/utils/errors' |
| 2 | +import { describeError, findCause, getErrorMessage, redactBoundParameters } from '@sim/utils/errors' |
3 | 3 | import type { NextRequest } from 'next/server' |
4 | 4 | import { NextResponse } from 'next/server' |
5 | 5 | import { getRateLimitHeaders } from '@/lib/api/server/rate-limit-context' |
@@ -93,12 +93,20 @@ function traceIdFromTraceparent(header: string | null | undefined): string | und |
93 | 93 | * message names the query. The shared describer reads the deepest cause and |
94 | 94 | * strips bound parameter values, so user data never reaches the log. |
95 | 95 | */ |
96 | | -function errorDetail(error: unknown): { cause?: string; code?: string } { |
| 96 | +function errorDetail(error: unknown): { cause?: string; code?: string; causeStack?: string } { |
97 | 97 | if (!(error instanceof Error) || error.cause === undefined) return {} |
98 | 98 | const described = describeError(error) |
| 99 | + /** Outside production the deepest cause's stack says where a wrapped failure was raised. */ |
| 100 | + const deepest = findCause( |
| 101 | + error, |
| 102 | + (candidate): candidate is Error => candidate instanceof Error && candidate.cause === undefined |
| 103 | + ) |
99 | 104 | return { |
100 | 105 | cause: `${described.name}: ${described.message}`, |
101 | 106 | ...(described.code ? { code: described.code } : {}), |
| 107 | + ...(process.env.NODE_ENV !== 'production' && deepest?.stack |
| 108 | + ? { causeStack: redactBoundParameters(deepest.stack) } |
| 109 | + : {}), |
102 | 110 | } |
103 | 111 | } |
104 | 112 |
|
|
0 commit comments