feat(core-shared): bindNoopInstrumentation + bindSentryInstrumentation
Adds DI binder functions for instrumentation. bindNoopInstrumentation binds NoopTracer + NoopLogger; bindSentryInstrumentation calls initSentryServer then binds SentryTracer + SentryLogger. Both are idempotent (unbind-first). Adds inversify + reflect-metadata as devDependencies so container tests compile. Re-exports from instrumentation barrel. 6 new tests (3+3). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,8 @@
|
||||
"@repo/core-testing": "workspace:*",
|
||||
"@repo/core-typescript": "workspace:*",
|
||||
"@types/node": "^22.0.0",
|
||||
"inversify": "^6.2.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"vitest": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// packages/core-shared/src/instrumentation/di/bind-noop-instrumentation.test.ts
|
||||
import "reflect-metadata";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { Container } from "inversify";
|
||||
import { bindNoopInstrumentation } from "@/instrumentation/di/bind-noop-instrumentation";
|
||||
import { INSTRUMENTATION_SYMBOLS } from "@/instrumentation/symbols";
|
||||
import { NoopTracer } from "@/instrumentation/noop-tracer";
|
||||
import { NoopLogger } from "@/instrumentation/noop-logger";
|
||||
import type { ITracer, ILogger } from "@/instrumentation";
|
||||
|
||||
describe("bindNoopInstrumentation", () => {
|
||||
it("returns a tracer + logger pair", () => {
|
||||
const c = new Container();
|
||||
const { tracer, logger } = bindNoopInstrumentation(c);
|
||||
expect(tracer).toBeInstanceOf(NoopTracer);
|
||||
expect(logger).toBeInstanceOf(NoopLogger);
|
||||
});
|
||||
|
||||
it("binds TRACER and LOGGER symbols on the container", () => {
|
||||
const c = new Container();
|
||||
bindNoopInstrumentation(c);
|
||||
const tracer = c.get<ITracer>(INSTRUMENTATION_SYMBOLS.TRACER);
|
||||
const logger = c.get<ILogger>(INSTRUMENTATION_SYMBOLS.LOGGER);
|
||||
expect(tracer).toBeInstanceOf(NoopTracer);
|
||||
expect(logger).toBeInstanceOf(NoopLogger);
|
||||
});
|
||||
|
||||
it("is idempotent — second call rebinds the same instances", () => {
|
||||
const c = new Container();
|
||||
const first = bindNoopInstrumentation(c);
|
||||
const second = bindNoopInstrumentation(c);
|
||||
// Implementations are NoopX, but instances may differ — that's fine
|
||||
expect(c.get(INSTRUMENTATION_SYMBOLS.TRACER)).toBe(second.tracer);
|
||||
expect(c.get(INSTRUMENTATION_SYMBOLS.LOGGER)).toBe(second.logger);
|
||||
expect(first.tracer).toBeInstanceOf(NoopTracer);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
// packages/core-shared/src/instrumentation/di/bind-noop-instrumentation.ts
|
||||
import type { Container } from "inversify";
|
||||
import { NoopTracer } from "../noop-tracer";
|
||||
import { NoopLogger } from "../noop-logger";
|
||||
import { INSTRUMENTATION_SYMBOLS } from "../symbols";
|
||||
import type { ITracer, ILogger } from "../index";
|
||||
|
||||
export function bindNoopInstrumentation(container: Container): {
|
||||
tracer: ITracer;
|
||||
logger: ILogger;
|
||||
} {
|
||||
const tracer = new NoopTracer();
|
||||
const logger = new NoopLogger();
|
||||
if (container.isBound(INSTRUMENTATION_SYMBOLS.TRACER)) {
|
||||
container.unbind(INSTRUMENTATION_SYMBOLS.TRACER);
|
||||
}
|
||||
if (container.isBound(INSTRUMENTATION_SYMBOLS.LOGGER)) {
|
||||
container.unbind(INSTRUMENTATION_SYMBOLS.LOGGER);
|
||||
}
|
||||
container.bind<ITracer>(INSTRUMENTATION_SYMBOLS.TRACER).toConstantValue(tracer);
|
||||
container.bind<ILogger>(INSTRUMENTATION_SYMBOLS.LOGGER).toConstantValue(logger);
|
||||
return { tracer, logger };
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// packages/core-shared/src/instrumentation/di/bind-sentry-instrumentation.test.ts
|
||||
import "reflect-metadata";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
|
||||
vi.mock("@sentry/nextjs", () => ({
|
||||
init: vi.fn(),
|
||||
startSpan: vi.fn((_opts: unknown, fn: (span: unknown) => unknown) =>
|
||||
fn({ setAttribute: vi.fn(), setStatus: vi.fn() }),
|
||||
),
|
||||
captureException: vi.fn(),
|
||||
captureMessage: vi.fn(),
|
||||
addBreadcrumb: vi.fn(),
|
||||
setUser: vi.fn(),
|
||||
replayIntegration: vi.fn(() => ({ name: "Replay" })),
|
||||
}));
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import { Container } from "inversify";
|
||||
import { bindSentryInstrumentation } from "@/instrumentation/di/bind-sentry-instrumentation";
|
||||
import { INSTRUMENTATION_SYMBOLS } from "@/instrumentation/symbols";
|
||||
import { SentryTracer } from "@/instrumentation/sentry/sentry-tracer";
|
||||
import { SentryLogger } from "@/instrumentation/sentry/sentry-logger";
|
||||
|
||||
describe("bindSentryInstrumentation", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("calls Sentry.init via initSentryServer", () => {
|
||||
const c = new Container();
|
||||
bindSentryInstrumentation(c, { dsn: "https://x@y/1", app: "web-next" });
|
||||
expect(Sentry.init).toHaveBeenCalledTimes(1);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
expect((Sentry.init as any).mock.calls[0][0].dsn).toBe("https://x@y/1");
|
||||
});
|
||||
|
||||
it("binds SentryTracer + SentryLogger to the container", () => {
|
||||
const c = new Container();
|
||||
bindSentryInstrumentation(c, { dsn: "https://x@y/1", app: "web-next" });
|
||||
expect(c.get(INSTRUMENTATION_SYMBOLS.TRACER)).toBeInstanceOf(SentryTracer);
|
||||
expect(c.get(INSTRUMENTATION_SYMBOLS.LOGGER)).toBeInstanceOf(SentryLogger);
|
||||
});
|
||||
|
||||
it("returns the tracer + logger instances", () => {
|
||||
const c = new Container();
|
||||
const { tracer, logger } = bindSentryInstrumentation(c, {
|
||||
dsn: "https://x@y/1",
|
||||
app: "web-next",
|
||||
});
|
||||
expect(tracer).toBeInstanceOf(SentryTracer);
|
||||
expect(logger).toBeInstanceOf(SentryLogger);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
// packages/core-shared/src/instrumentation/di/bind-sentry-instrumentation.ts
|
||||
import type { Container } from "inversify";
|
||||
import { SentryTracer } from "../sentry/sentry-tracer";
|
||||
import { SentryLogger } from "../sentry/sentry-logger";
|
||||
import { initSentryServer, type InitServerOpts } from "../sentry/init-server";
|
||||
import { INSTRUMENTATION_SYMBOLS } from "../symbols";
|
||||
import type { ITracer, ILogger } from "../index";
|
||||
|
||||
export type BindSentryOpts = InitServerOpts;
|
||||
|
||||
export function bindSentryInstrumentation(
|
||||
container: Container,
|
||||
opts: BindSentryOpts,
|
||||
): { tracer: ITracer; logger: ILogger } {
|
||||
initSentryServer(opts);
|
||||
|
||||
const tracer = new SentryTracer();
|
||||
const logger = new SentryLogger();
|
||||
|
||||
if (container.isBound(INSTRUMENTATION_SYMBOLS.TRACER)) {
|
||||
container.unbind(INSTRUMENTATION_SYMBOLS.TRACER);
|
||||
}
|
||||
if (container.isBound(INSTRUMENTATION_SYMBOLS.LOGGER)) {
|
||||
container.unbind(INSTRUMENTATION_SYMBOLS.LOGGER);
|
||||
}
|
||||
container.bind<ITracer>(INSTRUMENTATION_SYMBOLS.TRACER).toConstantValue(tracer);
|
||||
container.bind<ILogger>(INSTRUMENTATION_SYMBOLS.LOGGER).toConstantValue(logger);
|
||||
return { tracer, logger };
|
||||
}
|
||||
@@ -13,3 +13,8 @@ export { NoopTracer } from "./noop-tracer";
|
||||
export { NoopLogger } from "./noop-logger";
|
||||
export { withSpan } from "./with-span";
|
||||
export { INSTRUMENTATION_SYMBOLS } from "./symbols";
|
||||
export { bindNoopInstrumentation } from "./di/bind-noop-instrumentation";
|
||||
export {
|
||||
bindSentryInstrumentation,
|
||||
type BindSentryOpts,
|
||||
} from "./di/bind-sentry-instrumentation";
|
||||
|
||||
Reference in New Issue
Block a user