55 lines
2.1 KiB
TypeScript
55 lines
2.1 KiB
TypeScript
import type { ILogger } from "./logger.interface";
|
|
import type { Captured } from "../conformance/brands";
|
|
import { attachBrand } from "../conformance/brand-runtime";
|
|
import { isReported, markReported } from "./reported-flag";
|
|
|
|
/**
|
|
* Higher-order wrapper applied at DI bind time. Mirrors `withSpan`: takes a
|
|
* factory result `(args) => Promise<R>` and returns the same shape, but any
|
|
* thrown error is captured via `logger.captureException(err, { tags })` before
|
|
* being re-thrown.
|
|
*
|
|
* Skips capture if the error already carries the `__sentryReported` flag —
|
|
* this is what prevents double-capture when the same error bubbles through
|
|
* a wrapped repo → use case → controller chain (the repo's catch site
|
|
* captures first; outer wrappers see the flag and bail).
|
|
*
|
|
* Usage at bind time:
|
|
*
|
|
* const captured = withCapture(logger, { feature: "blog", layer: "use-case", name: "blog.getArticles" }, factory(deps));
|
|
* const wrapped = withSpan(tracer, opts, captured);
|
|
*
|
|
* Span wraps capture: the span timing reflects the captured-and-rethrown
|
|
* failure (errored span gets a duration), and the capture has accurate
|
|
* tags by the time it fires.
|
|
*/
|
|
export function withCapture<Args extends unknown[], R>(
|
|
logger: ILogger,
|
|
tags: Record<string, string>,
|
|
fn: (...args: Args) => Promise<R>,
|
|
): Captured<(...args: Args) => Promise<R>> {
|
|
const PROPAGATED_BRANDS = ["__instrumented", "__audited"] as const;
|
|
|
|
const wrapped: (...args: Args) => Promise<R> = async (...args) => {
|
|
try {
|
|
return await fn(...args);
|
|
} catch (err) {
|
|
if (!isReported(err)) {
|
|
logger.captureException(err, { tags });
|
|
markReported(err);
|
|
}
|
|
throw err;
|
|
}
|
|
};
|
|
// Propagate brands from the inner function (e.g. __audited from withAudit,
|
|
// __instrumented if already spanned) so the outermost binding carries all brands.
|
|
// __captured is omitted here because it is attached explicitly below.
|
|
for (const brand of PROPAGATED_BRANDS) {
|
|
if ((fn as unknown as Record<string, unknown>)[brand] === true) {
|
|
attachBrand(wrapped, brand);
|
|
}
|
|
}
|
|
attachBrand(wrapped, "__captured");
|
|
return wrapped as Captured<(...args: Args) => Promise<R>>;
|
|
}
|