UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

400 lines (347 loc) 13.9 kB
import { Alepha } from "alepha"; import { describe, it } from "vitest"; import { JsonFormatterProvider, LogDestinationProvider, LogFormatterProvider, MemoryDestinationProvider, PrettyFormatterProvider, RawFormatterProvider, } from "../index.ts"; import { Logger } from "../services/Logger.ts"; /** * Exposes the protected `formatter` getter so the runtime format override * can be asserted by provider identity. */ class TestLogger extends Logger { public get activeFormatter() { return this.formatter; } } describe("Logger", () => { const createLogger = (service = "TestService", module = "test.module") => { const alepha = Alepha.create({ env: { LOG_LEVEL: "info" }, }) .with({ provide: LogDestinationProvider, use: MemoryDestinationProvider, }) .with({ provide: LogFormatterProvider, use: PrettyFormatterProvider, }); return alepha.inject(Logger, { lifetime: "transient", args: [service, module], }); }; describe("parseLevel", () => { it("should return global level when no matching module found", ({ expect, }) => { const logger = createLogger(); // When no module-specific config matches, it falls back to global level expect(logger.parseLevel("debug", "non.matching.module")).toBe("DEBUG"); expect( logger.parseLevel("specific.module:trace,warn", "non.matching.module"), ).toBe("WARN"); }); it("should parse simple global level", ({ expect }) => { const logger = createLogger(); expect(logger.parseLevel("debug", "any.module")).toBe("DEBUG"); expect(logger.parseLevel("trace", "any.module")).toBe("TRACE"); expect(logger.parseLevel("warn", "any.module")).toBe("WARN"); expect(logger.parseLevel("error", "any.module")).toBe("ERROR"); expect(logger.parseLevel("silent", "any.module")).toBe("SILENT"); }); it("should parse module-specific level with colon separator", ({ expect, }) => { const logger = createLogger(); expect(logger.parseLevel("test:debug,info", "test.module")).toBe("DEBUG"); expect(logger.parseLevel("alepha:trace,warn", "alepha.core")).toBe( "TRACE", ); expect(logger.parseLevel("my.app:error,info", "my.app.service")).toBe( "ERROR", ); }); it("should parse module-specific level with equals separator", ({ expect, }) => { const logger = createLogger(); expect(logger.parseLevel("test=debug,info", "test.module")).toBe("DEBUG"); expect(logger.parseLevel("alepha=TRACE,warn", "alepha.core")).toBe( "TRACE", ); expect(logger.parseLevel("my.app=error,info", "my.app.service")).toBe( "ERROR", ); }); it("should handle multiple module configurations", ({ expect }) => { const logger = createLogger(); const config = "alepha.core:trace,alepha.server:debug,my.app:error,info"; expect(logger.parseLevel(config, "alepha.core")).toBe("TRACE"); expect(logger.parseLevel(config, "alepha.server")).toBe("DEBUG"); expect(logger.parseLevel(config, "my.app")).toBe("ERROR"); expect(logger.parseLevel(config, "other.module")).toBe("INFO"); }); it("should handle semicolon separators", ({ expect }) => { const logger = createLogger(); const config = "alepha:trace;my.app:error;info"; expect(logger.parseLevel(config, "alepha.core")).toBe("TRACE"); expect(logger.parseLevel(config, "my.app")).toBe("ERROR"); expect(logger.parseLevel(config, "other.module")).toBe("INFO"); }); it("should use startsWith matching for modules", ({ expect }) => { const logger = createLogger(); const config = "alepha:debug,info"; expect(logger.parseLevel(config, "alepha")).toBe("DEBUG"); expect(logger.parseLevel(config, "alepha.core")).toBe("DEBUG"); expect(logger.parseLevel(config, "alepha.server.cache")).toBe("DEBUG"); expect(logger.parseLevel(config, "other.alepha")).toBe("INFO"); }); it("should prioritize first matching module", ({ expect }) => { const logger = createLogger(); const config = "alepha:debug,alepha.core:trace,info"; // alepha.core matches both "alepha" and "alepha.core", but first match wins expect(logger.parseLevel(config, "alepha.core")).toBe("DEBUG"); }); it("should handle case insensitive levels", ({ expect }) => { const logger = createLogger(); expect(logger.parseLevel("DEBUG", "any.module")).toBe("DEBUG"); expect(logger.parseLevel("Test:TRACE,INFO", "test.module")).toBe("TRACE"); }); it("should handle whitespace around separators", ({ expect }) => { const logger = createLogger(); const config = " alepha : debug , my.app : error , info "; expect(logger.parseLevel(config, "alepha.core")).toBe("DEBUG"); expect(logger.parseLevel(config, "my.app")).toBe("ERROR"); expect(logger.parseLevel(config, "other")).toBe("INFO"); }); it("should fall back to global level when no module match", ({ expect, }) => { const logger = createLogger(); const config = "specific.module:debug,trace"; expect(logger.parseLevel(config, "other.module")).toBe("TRACE"); }); it("should handle empty parts gracefully", ({ expect }) => { const logger = createLogger(); // Empty parts are now skipped gracefully expect(logger.parseLevel(",,debug,,", "any.module")).toBe("DEBUG"); expect(logger.parseLevel("alepha:trace,,info", "alepha.core")).toBe( "TRACE", ); expect(logger.parseLevel("alepha:trace,,", "other.module")).toBe("INFO"); expect(logger.parseLevel(" , , debug , ", "any.module")).toBe( "DEBUG", ); }); it("should provide better error messages", ({ expect }) => { const logger = createLogger(); expect(() => logger.parseLevel("alepha:invalid,info", "alepha.core"), ).toThrow("Invalid log level 'invalid' for module pattern 'alepha'"); expect(() => logger.parseLevel("badlevel", "any.module")).toThrow( 'Invalid global log level "badlevel"', ); }); it("should support wildcard patterns", ({ expect }) => { const logger = createLogger(); // Basic wildcard matching expect(logger.parseLevel("alepha.*:debug,info", "alepha.core")).toBe( "DEBUG", ); expect(logger.parseLevel("alepha.*:debug,info", "alepha.server")).toBe( "DEBUG", ); expect(logger.parseLevel("alepha.*:debug,info", "other.module")).toBe( "INFO", ); // More specific patterns expect( logger.parseLevel("*.test:silent,*.core:trace,info", "alepha.test"), ).toBe("SILENT"); expect( logger.parseLevel("*.test:silent,*.core:trace,info", "my.core"), ).toBe("TRACE"); expect( logger.parseLevel("*.test:silent,*.core:trace,info", "other.module"), ).toBe("INFO"); // Exact prefix match still works (existing behavior) expect(logger.parseLevel("alepha.core:debug,info", "alepha.core")).toBe( "DEBUG", ); expect( logger.parseLevel("alepha.core:debug,info", "alepha.core.service"), ).toBe("DEBUG"); // startsWith behavior }); it("should prioritize more specific wildcard matches", ({ expect }) => { const logger = createLogger(); // First match wins, so order matters const config = "alepha.*:debug,alepha.core.*:trace,info"; expect(logger.parseLevel(config, "alepha.core.service")).toBe("DEBUG"); // matches alepha.* first // Reverse order const config2 = "alepha.core.*:trace,alepha.*:debug,info"; expect(logger.parseLevel(config2, "alepha.core.service")).toBe("TRACE"); // matches alepha.core.* first }); }); describe("asLogLevel", () => { it("should return valid log levels", ({ expect }) => { const logger = createLogger(); expect(logger.asLogLevel("trace")).toBe("TRACE"); expect(logger.asLogLevel("debug")).toBe("DEBUG"); expect(logger.asLogLevel("info")).toBe("INFO"); expect(logger.asLogLevel("warn")).toBe("WARN"); expect(logger.asLogLevel("error")).toBe("ERROR"); expect(logger.asLogLevel("silent")).toBe("SILENT"); }); it("should handle whitespace", ({ expect }) => { const logger = createLogger(); expect(logger.asLogLevel(" debug ")).toBe("DEBUG"); expect(logger.asLogLevel(" trace ")).toBe("TRACE"); }); it("should throw error for invalid levels", ({ expect }) => { const logger = createLogger(); expect(() => logger.asLogLevel("invalid")).toThrow( "Invalid log level: invalid", ); expect(() => logger.asLogLevel("")).toThrow("Invalid log level: "); expect(() => logger.asLogLevel("DEBUqG")).toThrow( "Invalid log level: DEBUqG", ); }); }); describe("level getter", () => { it("should update logLevel when state changes", ({ expect }) => { const alepha = Alepha.create({ env: { LOG_LEVEL: "info" }, }) .with({ provide: LogDestinationProvider, use: MemoryDestinationProvider, }) .with({ provide: LogFormatterProvider, use: PrettyFormatterProvider, }); const logger = alepha.inject(Logger, { lifetime: "transient", args: ["TestService", "test.module"], }); // Initial state expect(logger.level).toBe("INFO"); // Change state alepha.store.set("alepha.logger.level", "debug"); expect(logger.level).toBe("DEBUG"); // Change to module-specific config alepha.store.set("alepha.logger.level", "test:trace,warn"); expect(logger.level).toBe("TRACE"); }); it("should cache parsed level until state changes", ({ expect }) => { const alepha = Alepha.create({ env: { LOG_LEVEL: "info" }, }) .with({ provide: LogDestinationProvider, use: MemoryDestinationProvider, }) .with({ provide: LogFormatterProvider, use: PrettyFormatterProvider, }); const logger = alepha.inject(Logger, { lifetime: "transient", args: ["TestService", "test.module"], }); alepha.store.set("alepha.logger.level", "test:debug,info"); const level1 = logger.level; const level2 = logger.level; expect(level1).toBe("DEBUG"); expect(level2).toBe("DEBUG"); expect(level1).toBe(level2); // Should be same instance/value }); }); describe("format getter", () => { const make = () => { const alepha = Alepha.create({ env: { LOG_LEVEL: "info" } }) .with({ provide: LogDestinationProvider, use: MemoryDestinationProvider, }) .with({ provide: LogFormatterProvider, use: PrettyFormatterProvider, }); const logger = alepha.inject(TestLogger, { lifetime: "transient", args: ["TestService", "test.module"], }); return { alepha, logger }; }; it("falls back to the register-time formatter when unset", ({ expect }) => { const { logger } = make(); expect(logger.activeFormatter).toBeInstanceOf(PrettyFormatterProvider); }); it("honors the alepha.logger.format state override (read live)", ({ expect, }) => { const { alepha, logger } = make(); alepha.store.set("alepha.logger.format", "json"); expect(logger.activeFormatter).toBeInstanceOf(JsonFormatterProvider); alepha.store.set("alepha.logger.format", "raw"); expect(logger.activeFormatter).toBeInstanceOf(RawFormatterProvider); alepha.store.set("alepha.logger.format", "pretty"); expect(logger.activeFormatter).toBeInstanceOf(PrettyFormatterProvider); // Unknown / cleared → back to the register-time default. alepha.store.set("alepha.logger.format", undefined); expect(logger.activeFormatter).toBeInstanceOf(PrettyFormatterProvider); }); }); describe("isLevelEnabled", () => { const at = (level: string) => { const alepha = Alepha.create().with({ provide: LogDestinationProvider, use: MemoryDestinationProvider, }); const logger = alepha.inject(Logger, { lifetime: "transient", args: ["TestService", "test.module"], }); // Set after inject: the logger module's register defaults the level to // "trace" in test mode, so a pre-inject set would be overwritten. alepha.store.set("alepha.logger.level", level); return { alepha, logger }; }; it("enables a level only when it is at or above the active level", ({ expect, }) => { const { logger } = at("info"); expect(logger.isLevelEnabled("ERROR")).toBe(true); expect(logger.isLevelEnabled("WARN")).toBe(true); expect(logger.isLevelEnabled("INFO")).toBe(true); expect(logger.isLevelEnabled("DEBUG")).toBe(false); expect(logger.isLevelEnabled("TRACE")).toBe(false); }); it("enables DEBUG once the active level is debug, but not TRACE", ({ expect, }) => { const { logger } = at("debug"); expect(logger.isLevelEnabled("DEBUG")).toBe(true); expect(logger.isLevelEnabled("INFO")).toBe(true); expect(logger.isLevelEnabled("TRACE")).toBe(false); }); it("enables everything at trace and reacts to live state changes", ({ expect, }) => { const { alepha, logger } = at("trace"); expect(logger.isLevelEnabled("TRACE")).toBe(true); expect(logger.isLevelEnabled("DEBUG")).toBe(true); alepha.store.set("alepha.logger.level", "warn"); expect(logger.isLevelEnabled("DEBUG")).toBe(false); expect(logger.isLevelEnabled("WARN")).toBe(true); }); }); });