File size: 2,992 Bytes
1f21206 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | /**
* Diagnostics REST API
*
* GET /api/diagnostics/status — log directory, retention and counters
* GET /api/diagnostics/events — recent sanitized diagnostic events
* POST /api/diagnostics/events — append a sanitized client diagnostic event
* POST /api/diagnostics/export — write a sanitized tar.gz bundle
* POST /api/diagnostics/open-log-dir — open the diagnostics directory
* DELETE /api/diagnostics — clear diagnostics files
*/
import { diagnosticsService } from '../services/diagnosticsService.js'
import { ApiError, errorResponse } from '../middleware/errorHandler.js'
export async function handleDiagnosticsApi(
req: Request,
url: URL,
segments: string[],
): Promise<Response> {
try {
const action = segments[2]
if (!action && req.method === 'DELETE') {
await diagnosticsService.clear()
return Response.json({ ok: true })
}
if (action === 'status' && req.method === 'GET') {
return Response.json(await diagnosticsService.getStatus())
}
if (action === 'events' && req.method === 'GET') {
const limit = Number.parseInt(url.searchParams.get('limit') || '100', 10)
const events = await diagnosticsService.readRecentEvents(Number.isFinite(limit) ? limit : 100)
return Response.json({ events })
}
if (action === 'events' && req.method === 'POST') {
const body = await parseJsonBody(req)
const type = typeof body.type === 'string' && body.type.trim()
? body.type.trim().slice(0, 128)
: 'client_diagnostic_event'
const severity = isDiagnosticSeverity(body.severity) ? body.severity : 'error'
const summary = typeof body.summary === 'string' && body.summary.trim()
? body.summary
: type
const sessionId = typeof body.sessionId === 'string' ? body.sessionId : undefined
await diagnosticsService.recordEvent({
type,
severity,
summary,
sessionId,
details: body.details,
})
return Response.json({ ok: true })
}
if (action === 'export' && req.method === 'POST') {
return Response.json({ bundle: await diagnosticsService.exportBundle() })
}
if (action === 'open-log-dir' && req.method === 'POST') {
await diagnosticsService.openLogDir()
return Response.json({ ok: true })
}
throw new ApiError(404, `Unknown diagnostics endpoint: ${action ?? '(root)'}`, 'NOT_FOUND')
} catch (error) {
return errorResponse(error)
}
}
async function parseJsonBody(req: Request): Promise<Record<string, unknown>> {
try {
const body = await req.json()
return body && typeof body === 'object' ? body as Record<string, unknown> : {}
} catch {
throw ApiError.badRequest('Invalid JSON body')
}
}
function isDiagnosticSeverity(value: unknown): value is 'debug' | 'info' | 'warn' | 'error' {
return value === 'debug' || value === 'info' || value === 'warn' || value === 'error'
}
|