--- test_logger.lua — Demonstrates and tests all logger.lua features -- Adjust path if needed package.path = "./?.lua;" .. package.path local Logger = require("utils/logger") local PASS = 0 local FAIL = 0 local function test(label, cond) if cond then print(string.format(" [PASS] %s", label)) PASS = PASS + 1 else io.stderr:write(string.format(" [FAIL] %s\n", label)) FAIL = FAIL + 1 end end print("━━━ logger.lua test suite ━━━\n") -- ── 1. Basic levels ──────────────────────────────────────────────────────── print("▶ 1. Basic level output (INFO+)") do local handler, get, clear = Logger.handlers.MemoryHandler() local log = Logger.new("basic", { level = Logger.LEVELS.INFO, handlers = { handler } }) log:trace("should be filtered") log:debug("should be filtered") log:info("hello %s", "world") log:warn("disk %d%%", 90) log:error("boom") log:fatal("catastrophe") local records = get() test("TRACE filtered", records[1].level == Logger.LEVELS.INFO) test("INFO captured", records[1].message == "hello world") test("WARN captured", records[2].message == "disk 90%") test("ERROR captured", records[3].level == Logger.LEVELS.ERROR) test("FATAL captured", records[4].level == Logger.LEVELS.FATAL) test("exactly 4 records", #records == 4) end -- ── 2. Level filtering ───────────────────────────────────────────────────── print("\n▶ 2. Dynamic level changes") do local handler, get = Logger.handlers.MemoryHandler() local log = Logger.new("dyn", { handlers = { handler } }) log:set_level(Logger.LEVELS.DEBUG) log:debug("visible") log:set_level(Logger.LEVELS.ERROR) log:info("invisible") log:error("visible again") local records = get() test("DEBUG visible at DEBUG level", records[1].message == "visible") test("INFO invisible at ERROR level", records[2].message == "visible again") test("total 2 records", #records == 2) end -- ── 3. is_enabled ────────────────────────────────────────────────────────── print("\n▶ 3. is_enabled()") do local log = Logger.new("enabled", { level = Logger.LEVELS.WARN }) test("WARN enabled", log:is_enabled(Logger.LEVELS.WARN)) test("INFO disabled", not log:is_enabled(Logger.LEVELS.INFO)) test("ERROR enabled", log:is_enabled(Logger.LEVELS.ERROR)) end -- ── 4. Custom format ─────────────────────────────────────────────────────── print("\n▶ 4. Custom format string") do local handler, get = Logger.handlers.MemoryHandler() local log = Logger.new("fmt", { format = "{level}|{name}|{message}", handlers = { handler }, }) log:info("hi") local f = get()[1].formatted test("contains level", f:find("INFO") ~= nil) test("contains name", f:find("fmt") ~= nil) test("contains message", f:find("hi") ~= nil) test("pipe separator", f:find("|") ~= nil) end -- ── 5. Filters ───────────────────────────────────────────────────────────── print("\n▶ 5. Custom filter") do local handler, get = Logger.handlers.MemoryHandler() local log = Logger.new("filter", { level = Logger.LEVELS.DEBUG, handlers = { handler } }) -- only pass records whose message contains "keep" log:add_filter(function(rec) return rec.message:find("keep") ~= nil end) log:info("drop this") log:info("keep this") log:warn("also drop") log:debug("keep me too") local records = get() test("only 'keep' records pass", #records == 2) test("first kept record", records[1].message == "keep this") test("second kept record", records[2].message == "keep me too") end -- ── 6. Multiple handlers ─────────────────────────────────────────────────── print("\n▶ 6. Multiple handlers") do local h1, get1 = Logger.handlers.MemoryHandler() local h2, get2 = Logger.handlers.MemoryHandler() local log = Logger.new("multi", { handlers = { h1 } }) log:add_handler(h2) log:info("broadcast") test("handler1 received", #get1() == 1) test("handler2 received", #get2() == 1) test("same message", get1()[1].message == get2()[1].message) end -- ── 7. Child loggers ─────────────────────────────────────────────────────── print("\n▶ 7. Child loggers") do local handler, get = Logger.handlers.MemoryHandler() local parent = Logger.new("app", { handlers = { handler } }) local child = parent:child("db") parent:info("parent message") child:info("child message") local records = get() test("parent name", records[1].name == "app") test("child name", records[2].name == "app.db") test("child shares handler", #records == 2) end -- ── 8. Logger.get() registry ────────────────────────────────────────────── print("\n▶ 8. Logger.get() registry") do local a = Logger.get("svc.auth") local b = Logger.get("svc.auth") test("same instance returned", a == b) local c = Logger.get("svc.payments") test("different names differ", a ~= c) end -- ── 9. Format with no args ──────────────────────────────────────────────── print("\n▶ 9. Message with no format args") do local handler, get = Logger.handlers.MemoryHandler() local log = Logger.new("noargs", { handlers = { handler } }) log:info("plain string, no args") test("plain string works", get()[1].message == "plain string, no args") end -- ── 10. FileHandler ─────────────────────────────────────────────────────── print("\n▶ 10. FileHandler") do local path = "test_logger_output.log" os.remove(path) local fh = Logger.handlers.FileHandler({ path = path }) local log = Logger.new("file", { handlers = { fh } }) log:info("written to file") log:warn("second line") local f = io.open(path) local content = f and f:read("*a") or "" if f then f:close() end test("file created", content ~= "") test("INFO line present", content:find("written to file") ~= nil) test("WARN line present", content:find("second line") ~= nil) test("two newlines", select(2, content:gsub("\n","")) == 2) end -- ── 11. Console handler (visual) ────────────────────────────────────────── print("\n▶ 11. ConsoleHandler — visual check (colours enabled)") do local log = Logger.new("visual", { level = Logger.LEVELS.TRACE, handlers = { Logger.handlers.ConsoleHandler({ colours = true }) }, }) log:trace("trace message") log:debug("debug message") log:info ("info message") log:warn ("warn message") log:error("error message") log:fatal("fatal message") test("no crash", true) end -- ── Summary ──────────────────────────────────────────────────────────────── print(string.format("\n━━━ Results: %d passed, %d failed ━━━", PASS, FAIL)) if FAIL > 0 then os.exit(1) end