Skip to main content
Home
This release is 11 versions behind 1.0.4 — the latest version of @logtape/logtape. Jump to latest

Built and signed on GitHub Actions

Simple logging library with zero dependencies for Deno/Node.js/Bun/browsers

This package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers
This package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
This package works with Bun
This package works with Browsers
JSR Score
100%
Published
2 months ago (0.9.2)
Package root>config.test.ts
import { assertEquals } from "jsr:/@std/assert@^0.222.1/assert-equals"; import { assertRejects } from "jsr:/@std/assert@^0.222.1/assert-rejects"; import { assertStrictEquals } from "jsr:/@std/assert@^0.222.1/assert-strict-equals"; import { assertThrows } from "jsr:/@std/assert@^0.222.1/assert-throws"; import { type Config, ConfigError, configure, configureSync, getConfig, reset, resetSync, } from "./config.ts"; import type { Filter } from "./filter.ts"; import { getLogger, LoggerImpl } from "./logger.ts"; import type { LogRecord } from "./record.ts"; import type { Sink } from "./sink.ts"; Deno.test("configure()", async (t) => { let disposed = 0; await t.step("test", async () => { const aLogs: LogRecord[] = []; const a: Sink & AsyncDisposable = (record) => aLogs.push(record); a[Symbol.asyncDispose] = () => { ++disposed; return Promise.resolve(); }; const bLogs: LogRecord[] = []; const b: Sink & Disposable = (record) => bLogs.push(record); b[Symbol.dispose] = () => ++disposed; const cLogs: LogRecord[] = []; const c: Sink = cLogs.push.bind(cLogs); const x: Filter & AsyncDisposable = () => true; x[Symbol.asyncDispose] = () => { ++disposed; return Promise.resolve(); }; const y: Filter & Disposable = () => true; y[Symbol.dispose] = () => ++disposed; const config: Config<string, string> = { sinks: { a, b, c }, filters: { x, y, debug: "debug" }, loggers: [ { category: "my-app", sinks: ["a"], filters: ["x"], }, { category: ["my-app", "foo"], sinks: ["b"], parentSinks: "override", filters: ["y"], }, { category: ["my-app", "bar"], sinks: ["c"], filters: ["debug"], level: "info", // deprecated lowestLevel: "info", }, ], }; await configure(config); const logger = LoggerImpl.getLogger("my-app"); assertEquals(logger.sinks, [a]); assertEquals(logger.filters, [x]); assertEquals(logger.lowestLevel, "debug"); const foo = LoggerImpl.getLogger(["my-app", "foo"]); assertEquals(foo.sinks, [b]); assertEquals(foo.filters, [y]); assertEquals(foo.lowestLevel, "debug"); const bar = LoggerImpl.getLogger(["my-app", "bar"]); assertEquals(bar.sinks, [c]); assertEquals(bar.lowestLevel, "info"); bar.debug("ignored"); assertEquals(aLogs, []); assertEquals(bLogs, []); assertEquals(cLogs, []); foo.warn("logged"); assertEquals(aLogs, []); assertEquals(bLogs, [ { level: "warning", category: ["my-app", "foo"], message: ["logged"], rawMessage: "logged", properties: {}, timestamp: bLogs[0].timestamp, }, ]); assertEquals(cLogs, []); bar.info("logged"); assertEquals(aLogs, [ { level: "info", category: ["my-app", "bar"], message: ["logged"], rawMessage: "logged", properties: {}, timestamp: cLogs[0].timestamp, }, ]); assertEquals(bLogs, [ { level: "warning", category: ["my-app", "foo"], message: ["logged"], rawMessage: "logged", properties: {}, timestamp: bLogs[0].timestamp, }, ]); assertEquals(cLogs, [ { level: "info", category: ["my-app", "bar"], message: ["logged"], rawMessage: "logged", properties: {}, timestamp: cLogs[0].timestamp, }, ]); assertStrictEquals(getConfig(), config); }); await t.step("reconfigure", async () => { await assertRejects( () => configure({ sinks: {}, loggers: [{ category: "my-app" }], }), ConfigError, "Already configured", ); assertEquals(disposed, 0); // No exception if reset is true: const config = { sinks: {}, loggers: [{ category: "my-app" }], reset: true, }; await configure(config); assertEquals(disposed, 4); assertStrictEquals(getConfig(), config); }); await t.step("tear down", async () => { await reset(); assertStrictEquals(getConfig(), null); }); await t.step("lowestLevel", async () => { const a: LogRecord[] = []; const b: LogRecord[] = []; const c: LogRecord[] = []; await configure({ sinks: { a: a.push.bind(a), b: b.push.bind(b), c: c.push.bind(c), }, loggers: [ { category: "foo", sinks: ["a"], lowestLevel: "info" }, { category: ["foo", "bar"], sinks: ["b"], lowestLevel: "warning" }, { category: ["foo", "baz"], sinks: ["c"], lowestLevel: "debug" }, { category: ["logtape", "meta"], sinks: [] }, ], }); getLogger(["foo", "bar"]).warn("test"); assertEquals(a.length, 1); assertEquals(b.length, 1); while (a.length > 0) a.pop(); while (b.length > 0) b.pop(); getLogger(["foo", "baz"]).debug("test"); assertEquals(a.length, 0); assertEquals(c.length, 1); while (a.length > 0) a.pop(); while (c.length > 0) c.pop(); }); await t.step("tear down", async () => { await reset(); assertStrictEquals(getConfig(), null); }); await t.step("misconfiguration", async () => { await assertRejects( () => configure({ // deno-lint-ignore no-explicit-any sinks: {} as any, loggers: [ { category: "my-app", sinks: ["invalid"], }, ], reset: true, }), ConfigError, "Sink not found: invalid", ); assertStrictEquals(getConfig(), null); await assertRejects( () => configure({ sinks: {}, // deno-lint-ignore no-explicit-any filters: {} as any, loggers: [ { category: "my-app", filters: ["invalid"], }, ], reset: true, }), ConfigError, "Filter not found: invalid", ); assertStrictEquals(getConfig(), null); }); const metaCategories = [[], ["logtape"], ["logtape", "meta"]]; for (const metaCategory of metaCategories) { await t.step( "meta configuration: " + JSON.stringify(metaCategory), async () => { const config = { sinks: {}, loggers: [ { category: metaCategory, sinks: [], filters: [], }, ], }; await configure(config); assertEquals(LoggerImpl.getLogger(["logger", "meta"]).sinks, []); assertStrictEquals(getConfig(), config); }, ); await t.step("tear down", async () => { await reset(); assertStrictEquals(getConfig(), null); }); } }); Deno.test("configureSync()", async (t) => { let disposed = 0; await t.step("test", () => { const bLogs: LogRecord[] = []; const b: Sink & Disposable = (record) => bLogs.push(record); b[Symbol.dispose] = () => ++disposed; const cLogs: LogRecord[] = []; const c: Sink = cLogs.push.bind(cLogs); const y: Filter & Disposable = () => true; y[Symbol.dispose] = () => ++disposed; const config: Config<string, string> = { sinks: { b, c }, filters: { y, debug: "debug" }, loggers: [ { category: ["my-app", "foo"], sinks: ["b"], parentSinks: "override", filters: ["y"], }, { category: ["my-app", "bar"], sinks: ["c"], filters: ["debug"], level: "info", // deprecated lowestLevel: "info", }, ], }; configureSync(config); const foo = LoggerImpl.getLogger(["my-app", "foo"]); assertEquals(foo.sinks, [b]); assertEquals(foo.filters, [y]); assertEquals(foo.lowestLevel, "debug"); const bar = LoggerImpl.getLogger(["my-app", "bar"]); assertEquals(bar.sinks, [c]); assertEquals(bar.lowestLevel, "info"); bar.debug("ignored"); assertEquals(bLogs, []); assertEquals(cLogs, []); foo.warn("logged"); assertEquals(bLogs, [ { level: "warning", category: ["my-app", "foo"], message: ["logged"], rawMessage: "logged", properties: {}, timestamp: bLogs[0].timestamp, }, ]); assertEquals(cLogs, []); bar.info("logged"); assertEquals(bLogs, [ { level: "warning", category: ["my-app", "foo"], message: ["logged"], rawMessage: "logged", properties: {}, timestamp: bLogs[0].timestamp, }, ]); assertEquals(cLogs, [ { level: "info", category: ["my-app", "bar"], message: ["logged"], rawMessage: "logged", properties: {}, timestamp: cLogs[0].timestamp, }, ]); assertStrictEquals(getConfig(), config); }); await t.step("reconfigure", () => { assertThrows( () => configureSync({ sinks: {}, loggers: [{ category: "my-app" }], }), ConfigError, "Already configured", ); assertEquals(disposed, 0); // No exception if reset is true: const config = { sinks: {}, loggers: [{ category: "my-app" }], reset: true, }; configureSync(config); assertEquals(disposed, 2); assertStrictEquals(getConfig(), config); }); await t.step("tear down", () => { resetSync(); assertStrictEquals(getConfig(), null); }); await t.step("misconfiguration", () => { assertThrows( () => configureSync({ // deno-lint-ignore no-explicit-any sinks: {} as any, loggers: [ { category: "my-app", sinks: ["invalid"], }, ], reset: true, }), ConfigError, "Sink not found: invalid", ); assertStrictEquals(getConfig(), null); assertThrows( () => configureSync({ sinks: {}, // deno-lint-ignore no-explicit-any filters: {} as any, loggers: [ { category: "my-app", filters: ["invalid"], }, ], reset: true, }), ConfigError, "Filter not found: invalid", ); assertStrictEquals(getConfig(), null); }); const metaCategories = [[], ["logtape"], ["logtape", "meta"]]; for (const metaCategory of metaCategories) { await t.step("meta configuration: " + JSON.stringify(metaCategory), () => { const config = { sinks: {}, loggers: [ { category: metaCategory, sinks: [], filters: [], }, ], }; configureSync(config); assertEquals(LoggerImpl.getLogger(["logger", "meta"]).sinks, []); assertStrictEquals(getConfig(), config); }); await t.step("tear down", () => { resetSync(); assertStrictEquals(getConfig(), null); }); } await t.step("no async sinks", () => { const aLogs: LogRecord[] = []; const a: Sink & AsyncDisposable = (record) => aLogs.push(record); a[Symbol.asyncDispose] = () => { return Promise.resolve(); }; const config: Config<string, string> = { sinks: { a }, loggers: [ { category: "my-app", sinks: ["a"], }, ], }; assertThrows( () => configureSync(config), ConfigError, "Async disposables cannot be used with configureSync()", ); assertStrictEquals(getConfig(), null); }); await t.step("no async filters", () => { const aLogs: LogRecord[] = []; const a: Sink & Disposable = (record) => aLogs.push(record); a[Symbol.dispose] = () => ++disposed; const x: Filter & AsyncDisposable = () => true; x[Symbol.asyncDispose] = () => { ++disposed; return Promise.resolve(); }; const config: Config<string, string> = { sinks: { a }, filters: { x }, loggers: [ { category: "my-app", sinks: ["a"], filters: ["x"], }, ], }; assertThrows( () => configureSync(config), ConfigError, "Async disposables cannot be used with configureSync()", ); assertStrictEquals(getConfig(), null); }); await t.step("cannot reset async disposables", async () => { const aLogs: LogRecord[] = []; const a: Sink & AsyncDisposable = (record) => aLogs.push(record); a[Symbol.asyncDispose] = () => { ++disposed; return Promise.resolve(); }; await configure({ sinks: { a }, loggers: [{ category: "my-app", sinks: ["a"] }], }); assertThrows( () => configureSync({ sinks: { a(record) { aLogs.push(record); }, }, loggers: [{ category: "my-app", sinks: ["a"] }], reset: true, }), ConfigError, "Previously configured async disposables are still active", ); }); });