UNPKG

@beignet/core

Version:

Core framework primitives for Beignet

177 lines 6.04 kB
/** * Error reporting hook utilities for @beignet/core/server */ import { DEFAULT_ERROR_REPORTING_TIMEOUT_MS, tryReportException, } from "../../error-reporting/index.js"; import { errorReportingObserverTimeout, runErrorReportingOperation, } from "../../error-reporting/internal.js"; import { isAppError, SchemaValidationError } from "../../errors/index.js"; import { IdempotencyConflictError, IdempotencyInProgressError, } from "../../idempotency/index.js"; import { AuthUnauthorizedError, EntitlementRequiredError, GateAuthorizationError, TenantRequiredError, } from "../../ports/index.js"; /** * Create server hooks that capture unexpected HTTP request failures through * `ctx.ports.errorReporter`. * * The hook observes `onCaughtError`, never maps responses, and ignores capture * failures by default so reporting cannot affect request handling. */ export function createErrorReportingHooks(config = {}) { return { name: "error-reporting", async onCaughtError(args) { const timeoutMs = config.timeoutMs ?? DEFAULT_ERROR_REPORTING_TIMEOUT_MS; let prepared; try { prepared = await runErrorReportingOperation(async () => { const shouldReport = config.shouldReport ? await config.shouldReport(args) : shouldReportServerError(args.err); if (!shouldReport) return undefined; const reporter = await resolveReporter(config.reporter, args); if (!reporter) return undefined; const baseOptions = createDefaultReportOptions(args); const customOptions = typeof config.reportOptions === "function" ? await config.reportOptions(args) : config.reportOptions; return { reporter, reportOptions: mergeReportOptions(baseOptions, customOptions), }; }, timeoutMs); } catch (reportingError) { await notifyReporterError(config, args, reportingError, timeoutMs); return; } if (!prepared) return; await tryReportException({ reporter: prepared.reporter, error: args.err, reportOptions: prepared.reportOptions, timeoutMs, onReporterError: ({ reportingError }) => config.onReporterError?.({ error: args.err, reportingError, ctx: args.ctx, req: args.req, contract: args.contract, }), }); }, }; } async function notifyReporterError(config, args, reportingError, timeoutMs) { if (!config.onReporterError) return; try { await runErrorReportingOperation(() => config.onReporterError?.({ error: args.err, reportingError, ctx: args.ctx, req: args.req, contract: args.contract, }), errorReportingObserverTimeout(timeoutMs)); } catch { // Reporting observers must not affect request handling. } } /** * Default filter for HTTP error reporting. */ export function shouldReportServerError(error) { if (isAppError(error)) return false; if (error instanceof AuthUnauthorizedError) return false; if (error instanceof TenantRequiredError) return false; if (error instanceof IdempotencyConflictError) return false; if (error instanceof IdempotencyInProgressError) return false; if (error instanceof GateAuthorizationError) return false; if (error instanceof EntitlementRequiredError) return false; if (error instanceof SchemaValidationError) return false; return true; } async function resolveReporter(reporter, args) { if (typeof reporter === "function") { return reporter(args); } if (reporter) { return reporter; } return args.ctx?.ports?.errorReporter; } function createDefaultReportOptions(args) { const ctx = args.ctx; const url = safeUrl(args.req.url); const actor = ctx?.actor; const tenant = ctx?.tenant; return { level: "error", mechanism: "beignet.server", handled: false, requestId: ctx?.requestId, traceId: ctx?.traceId, spanId: ctx?.spanId, parentSpanId: ctx?.parentSpanId, traceparent: ctx?.traceparent, user: actor?.type === "user" && actor.id ? { id: actor.id } : undefined, tags: { "beignet.kind": "http", "http.method": args.req.method, "http.route": args.contract.path, "http.contract": args.contract.name, }, contexts: { request: { method: args.req.method, path: url?.pathname ?? args.contract.path, route: args.contract.path, contract: args.contract.name, }, actor: actor?.type || actor?.id ? { type: actor.type, id: actor.id, } : undefined, tenant: tenant?.id ? { id: tenant.id } : undefined, }, }; } function mergeReportOptions(base, custom) { if (!custom) return base; return { ...base, ...custom, tags: { ...base.tags, ...custom.tags, }, contexts: { ...base.contexts, ...custom.contexts, }, extra: { ...base.extra, ...custom.extra, }, }; } function safeUrl(value) { try { return new URL(value); } catch { return undefined; } } //# sourceMappingURL=error-reporting.js.map