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-testing": "workspace:*",
|
||||||
"@repo/core-typescript": "workspace:*",
|
"@repo/core-typescript": "workspace:*",
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
|
"inversify": "^6.2.0",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
"vitest": "^3.1.0"
|
"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 { NoopLogger } from "./noop-logger";
|
||||||
export { withSpan } from "./with-span";
|
export { withSpan } from "./with-span";
|
||||||
export { INSTRUMENTATION_SYMBOLS } from "./symbols";
|
export { INSTRUMENTATION_SYMBOLS } from "./symbols";
|
||||||
|
export { bindNoopInstrumentation } from "./di/bind-noop-instrumentation";
|
||||||
|
export {
|
||||||
|
bindSentryInstrumentation,
|
||||||
|
type BindSentryOpts,
|
||||||
|
} from "./di/bind-sentry-instrumentation";
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -509,6 +509,12 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.0.0
|
specifier: ^22.0.0
|
||||||
version: 22.19.17
|
version: 22.19.17
|
||||||
|
inversify:
|
||||||
|
specifier: ^6.2.0
|
||||||
|
version: 6.2.2(reflect-metadata@0.2.2)
|
||||||
|
reflect-metadata:
|
||||||
|
specifier: ^0.2.2
|
||||||
|
version: 0.2.2
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.17)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.2)(tsx@4.21.0)
|
version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.17)(happy-dom@20.8.9)(jiti@2.6.1)(jsdom@25.0.1)(lightningcss@1.32.0)(sass@1.99.0)(terser@5.46.2)(tsx@4.21.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user