import { describe, expect, it, vi } from "vitest"; import { createLogger } from "../src/index"; import { createSentrySink, toSentryLogPayload } from "../src/sentry"; function createFakeSentry() { const scope = { setLevel: vi.fn(), setTag: vi.fn(), setExtras: vi.fn() }; return { scope, sentry: { withScope: (callback: (value: typeof scope) => void) => callback(scope), captureException: vi.fn(), captureMessage: vi.fn(), logger: { trace: vi.fn(), debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), }, }, }; } describe("toSentryLogPayload", () => { it("joins string messages and flattens primitive object attributes", () => { expect( toSentryLogPayload({ level: "info", namespace: "API", arguments: ["Cache warmed", { entries: 42, ok: true, nested: { a: 1 } }], timestamp: new Date(), environment: "production", sendToSentryLogs: true, sendToSentryIssue: false, sendToConsole: false, }), ).toEqual({ message: "Cache warmed", attributes: { "logger.namespace": "API", entries: 42, ok: true, }, }); }); }); describe("createSentrySink", () => { it("captures errors as Issues with namespace and structured attributes", () => { const { scope, sentry } = createFakeSentry(); const logger = createLogger({ environment: "production", namespace: "API", sinks: [createSentrySink(sentry)], }); const error = new Error("offline"); logger.warn("ignored without logs"); logger.error("Request failed", error, { requestId: "req_1" }); expect(sentry.captureException).toHaveBeenCalledWith(error); expect(sentry.captureMessage).not.toHaveBeenCalled(); expect(sentry.logger.info).not.toHaveBeenCalled(); expect(scope.setLevel).toHaveBeenCalledWith("error"); expect(scope.setTag).toHaveBeenCalledWith("logger.namespace", "API"); expect(scope.setExtras).toHaveBeenCalledWith({ "logger.namespace": "API", requestId: "req_1", }); }); it("writes non-error events to Sentry Logs when enabled, not as Issues", () => { const { sentry } = createFakeSentry(); const logger = createLogger({ environment: "production", namespace: "API", sinks: [createSentrySink(sentry, { logs: true })], }); logger.warn("slow", { ms: 1200 }); logger.info("forced", { id: 1 }, { sentry: true }); expect(sentry.captureMessage).not.toHaveBeenCalled(); expect(sentry.captureException).not.toHaveBeenCalled(); expect(sentry.logger.warn).toHaveBeenCalledWith("slow", { "logger.namespace": "API", ms: 1200, }); expect(sentry.logger.info).toHaveBeenCalledWith("forced", { "logger.namespace": "API", id: 1, }); }); it("does not create an Issue when suppressSentry is set", () => { const { sentry } = createFakeSentry(); const logger = createLogger({ environment: "production", runtime: "node", sinks: [createSentrySink(sentry, { logs: true })], }); logger.error("expected", new Error("nope"), { suppressSentry: true }); expect(sentry.captureException).not.toHaveBeenCalled(); expect(sentry.logger.error).not.toHaveBeenCalled(); }); it("never sends in development", () => { const { sentry } = createFakeSentry(); const logger = createLogger({ environment: "development", sinks: [createSentrySink(sentry, { logs: true })], }); logger.error("local", new Error("x")); logger.warn("local warn", { sentry: true }); expect(sentry.captureException).not.toHaveBeenCalled(); expect(sentry.logger.warn).not.toHaveBeenCalled(); }); });