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:
2026-05-07 00:01:54 +02:00
parent b08da12447
commit fbcee9b9f2
7 changed files with 155 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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";