115 lines
4.2 KiB
TypeScript
115 lines
4.2 KiB
TypeScript
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();
|
|
});
|
|
});
|