mirror of
https://github.com/saltbo/zpan.git
synced 2026-08-29 00:01:42 +08:00
fix(server): log underlying cause chain for failed D1 queries
Drizzle wraps the real D1 error in DrizzleQueryError.cause; both the access log and the origin-detect catch only logged .message, surfacing just "Failed query: <sql>" with no reason. Add formatError() to flatten the cause chain and use it at both sites so D1 failures are diagnosable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+3
-4
@@ -3,6 +3,7 @@ import type { Context } from 'hono'
|
||||
import { Hono } from 'hono'
|
||||
import { cors } from 'hono/cors'
|
||||
import type { Auth } from './auth'
|
||||
import { formatError } from './lib/errors'
|
||||
import { authMiddleware } from './middleware/auth'
|
||||
import { imageHostingDomain } from './middleware/image-hosting-domain'
|
||||
import { accessLog } from './middleware/logger'
|
||||
@@ -50,8 +51,7 @@ export function createApp(platform: Platform, auth: Auth) {
|
||||
app.use('/*', platformMiddleware(platform, auth))
|
||||
app.use('/*', async (c, next) => {
|
||||
const result = await ensureSitePublicOrigin(platform.db, c.req.url).catch((err) => {
|
||||
const code = err instanceof Error ? err.message : String(err)
|
||||
console.error(`site.public_origin.detect.error code=${code}`)
|
||||
console.error(`site.public_origin.detect.error code=${formatError(err)}`)
|
||||
return { origin: null, created: false }
|
||||
})
|
||||
|
||||
@@ -66,8 +66,7 @@ export function createApp(platform: Platform, auth: Auth) {
|
||||
trigger: 'runtime',
|
||||
runtime: instanceTelemetryRuntime(platform),
|
||||
}).catch((err) => {
|
||||
const code = err instanceof Error ? err.message : String(err)
|
||||
console.error(`instance.telemetry.initial_report.error code=${code}`)
|
||||
console.error(`instance.telemetry.initial_report.error code=${formatError(err)}`)
|
||||
})
|
||||
waitUntil(c, task)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { formatError } from './errors'
|
||||
|
||||
describe('formatError', () => {
|
||||
it('returns the message for a plain error', () => {
|
||||
expect(formatError(new Error('boom'))).toBe('boom')
|
||||
})
|
||||
|
||||
it('flattens the cause chain (drizzle wraps the real D1 error in cause)', () => {
|
||||
const d1 = new Error('D1_ERROR: Network connection lost')
|
||||
const wrapped = new Error('Failed query: update "download_tasks" ...', { cause: d1 })
|
||||
expect(formatError(wrapped)).toBe('Failed query: update "download_tasks" ... <- D1_ERROR: Network connection lost')
|
||||
})
|
||||
|
||||
it('appends a non-Error cause', () => {
|
||||
const err = new Error('outer', { cause: 'inner string' })
|
||||
expect(formatError(err)).toBe('outer <- inner string')
|
||||
})
|
||||
|
||||
it('stringifies a non-Error value', () => {
|
||||
expect(formatError('plain string')).toBe('plain string')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Flatten an error and its `cause` chain into a single string.
|
||||
*
|
||||
* Drizzle wraps the underlying driver error in `DrizzleQueryError`, whose
|
||||
* `message` is only `Failed query: <sql>`; the real D1 error lives in `.cause`.
|
||||
* Logging just `.message` hides why the query failed — this surfaces the chain.
|
||||
*/
|
||||
export function formatError(error: unknown): string {
|
||||
if (!(error instanceof Error)) return String(error)
|
||||
|
||||
const parts: string[] = [error.message]
|
||||
let cause = (error as { cause?: unknown }).cause
|
||||
while (cause instanceof Error) {
|
||||
parts.push(cause.message)
|
||||
cause = (cause as { cause?: unknown }).cause
|
||||
}
|
||||
if (cause !== undefined) parts.push(String(cause))
|
||||
|
||||
return parts.join(' <- ')
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Context } from 'hono'
|
||||
import { createMiddleware } from 'hono/factory'
|
||||
import { formatError } from '../lib/errors'
|
||||
import type { Env } from './platform'
|
||||
|
||||
export const accessLog = createMiddleware<Env>(async (c, next) => {
|
||||
@@ -44,10 +45,8 @@ function accessLogFields(
|
||||
)
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
fields.push(['error', error.message])
|
||||
} else if (error) {
|
||||
fields.push(['error', String(error)])
|
||||
if (error !== undefined) {
|
||||
fields.push(['error', formatError(error)])
|
||||
}
|
||||
|
||||
return fields
|
||||
|
||||
Reference in New Issue
Block a user