Files
agentic-dev/packages/core-shared/src/instrumentation/with-capture.ts

43 lines
1.6 KiB
TypeScript

import type { ILogger } from "./logger.interface";
import type { Captured } from "../conformance/brands";
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 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;
}
};
return wrapped as Captured<(...args: Args) => Promise<R>>;
}