@@ -0,0 +1,94 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createLogger, parseLoggingOverride } from "../src/index.js";
|
||||
import type { LogEvent, LogSink } from "../src/index.js";
|
||||
|
||||
function sink(): { sink: LogSink; events: LogEvent[] } {
|
||||
const events: LogEvent[] = [];
|
||||
return { events, sink: { emit: (event) => events.push(event) } };
|
||||
}
|
||||
|
||||
describe("parseLoggingOverride", () => {
|
||||
it("accepts a global level and a namespace-filtered level", () => {
|
||||
expect(parseLoggingOverride("debug")).toEqual({ level: "debug", namespaces: [] });
|
||||
expect(parseLoggingOverride("debug:WIDGET, API")).toEqual({
|
||||
level: "debug",
|
||||
namespaces: ["WIDGET", "API"],
|
||||
});
|
||||
expect(parseLoggingOverride("verbose")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("createLogger", () => {
|
||||
it("uses environment defaults and never evaluates suppressed lazy data", () => {
|
||||
const destination = sink();
|
||||
const expensive = vi.fn(() => ({ huge: "snapshot" }));
|
||||
const logger = createLogger({ environment: "production", sinks: [destination.sink] });
|
||||
logger.debug("ignored", expensive);
|
||||
logger.error("kept", expensive);
|
||||
expect(expensive).toHaveBeenCalledTimes(1);
|
||||
expect(destination.events).toHaveLength(1);
|
||||
expect(destination.events[0]?.arguments).toEqual(["kept", { huge: "snapshot" }]);
|
||||
});
|
||||
|
||||
it("applies session namespace filtering to namespace descendants", () => {
|
||||
const destination = sink();
|
||||
const storage = { getItem: () => "debug:WIDGET" };
|
||||
createLogger({
|
||||
environment: "production",
|
||||
namespace: "WIDGET:Button",
|
||||
sessionStorage: storage,
|
||||
sinks: [destination.sink],
|
||||
}).debug("shown");
|
||||
createLogger({
|
||||
environment: "production",
|
||||
namespace: "API",
|
||||
sessionStorage: storage,
|
||||
sinks: [destination.sink],
|
||||
}).debug("hidden");
|
||||
expect(destination.events.map((event) => event.arguments[0])).toEqual(["shown"]);
|
||||
});
|
||||
|
||||
it("builds immutable child namespaces and reports failed assertions as errors", () => {
|
||||
const destination = sink();
|
||||
const root = createLogger({
|
||||
environment: "development",
|
||||
namespace: "WIDGET",
|
||||
sinks: [destination.sink],
|
||||
});
|
||||
root.child("Button").assert(false, "missing label");
|
||||
expect(root.namespace).toBe("WIDGET");
|
||||
expect(destination.events[0]).toMatchObject({
|
||||
level: "error",
|
||||
namespace: "WIDGET:Button",
|
||||
arguments: ["Assertion failed", "missing label"],
|
||||
});
|
||||
});
|
||||
|
||||
it("sends production browser errors to Sentry without writing to the console", () => {
|
||||
const destination = sink();
|
||||
const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
||||
const logger = createLogger({
|
||||
environment: "production",
|
||||
runtime: "browser",
|
||||
sentry: destination.sink,
|
||||
});
|
||||
logger.error("captured");
|
||||
expect(destination.events).toHaveLength(1);
|
||||
expect(consoleError).not.toHaveBeenCalled();
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
|
||||
it("keeps production Node errors on stderr as well as sending them to Sentry", () => {
|
||||
const destination = sink();
|
||||
const consoleError = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
||||
const logger = createLogger({
|
||||
environment: "production",
|
||||
runtime: "node",
|
||||
sentry: destination.sink,
|
||||
});
|
||||
logger.error("captured");
|
||||
expect(destination.events).toHaveLength(1);
|
||||
expect(consoleError).toHaveBeenCalledTimes(1);
|
||||
consoleError.mockRestore();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createLogger } from "../src/index.js";
|
||||
import { createSentrySink } from "../src/sentry.js";
|
||||
|
||||
describe("createSentrySink", () => {
|
||||
it("captures errors with namespace and structured context, but ignores warnings", () => {
|
||||
const scope = { setLevel: vi.fn(), setTag: vi.fn(), setExtras: vi.fn() };
|
||||
const sentry = {
|
||||
withScope: (callback: (value: typeof scope) => void) => callback(scope),
|
||||
captureException: vi.fn(),
|
||||
captureMessage: vi.fn(),
|
||||
};
|
||||
const logger = createLogger({
|
||||
environment: "production",
|
||||
namespace: "API",
|
||||
sinks: [createSentrySink(sentry)],
|
||||
});
|
||||
const error = new Error("offline");
|
||||
logger.warn("ignored");
|
||||
logger.error("Request failed", error, { requestId: "req_1" });
|
||||
expect(sentry.captureException).toHaveBeenCalledWith(error);
|
||||
expect(scope.setTag).toHaveBeenCalledWith("logger.namespace", "API");
|
||||
expect(scope.setExtras).toHaveBeenCalledWith({ context_0: { requestId: "req_1" } });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user