@beignet/core
Version:
Core framework primitives for Beignet
177 lines • 6.04 kB
JavaScript
/**
* 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