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

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>>;
}