This release is 11 versions behind 1.0.4 — the latest version of @logtape/logtape. Jump to latest
Built and signed on GitHub ActionsBuilt and signed on GitHub Actions
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




JSR Score
100%
Published
2 months ago (0.9.2)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512import { 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", ); }); });