
LoggerTest.pudu
Pudu108 lines5.5 KB
1/** @Test.Logger.Suite — the pipeline from a write to its sinks */2module PuduLangLog.LoggerTest34import Std.Io as Io5import Std.Sync as Sync6import Std.Test as Test7import PuduLangLog.Clock as Clock8import PuduLangLog.Configuration as Configuration9import PuduLangLog.Event as Event10import PuduLangLog.LevelSwitch as LevelSwitch11import PuduLangLog as Log12import PuduLangLog.Logger as Logger13import PuduLangLog.SelfLog as SelfLog14import PuduLangLog.Sinks.Memory as Memory15import PuduLangLog.Value as Value161718const NOW: Log.Timestamp = Log.Timestamp{millis: 1790604309123, offset: 120}192021fn base(memory: &Memory.Memory) -> Configuration.Configuration {22 Configuration.create().clock(Clock.fixed(NOW)).writeTo(Memory.sink(memory))23}242526fn collecting(lines: &Sync.Cell[Array[Str]]) -> SelfLog.SelfLog {27 let log = SelfLog.create()28 let shared = *lines29 SelfLog.enable(&log, fn(line: Str) -> () {30 if let Ok(held) = Sync.get(&shared) { let _stored = Sync.set(&shared, held.push(line)) }31 })32 log33}343536fn linesOf(lines: &Sync.Cell[Array[Str]]) -> Array[Str] {37 match Sync.get(lines) {38 case Ok(held) => held39 case Err(_) => []40 }41}424344fn main() -> Int {45 let memory = Memory.create()46 let logger = base(&memory).createLogger()47 logger.information("Disk \{Disk\} is \{Percent\}% full", [Value.text("C"), Value.int(93)])48 logger.debug("Hidden", [])49 let first = Memory.events(&memory)[0]5051 let switchMemory = Memory.create()52 let control = LevelSwitch.create(Log.Warning)53 let changes = Sync.cell(0)54 LevelSwitch.onChange(&control, fn(_old: Log.Level, _new: Log.Level) -> () {55 if let Ok(count) = Sync.get(&changes) { let _stored = Sync.set(&changes, count + 1) }56 })57 let switched = base(&switchMemory).controlledBy(&control).createLogger()58 switched.information("before", [])59 LevelSwitch.set(&control, Log.Debug)60 LevelSwitch.set(&control, Log.Debug)61 switched.information("after", [])6263 let overrideMemory = Memory.create()64 let overridden = base(&overrideMemory).overrideLevel("App.Noisy", Log.Warning).overrideLevel("App.Noisy.Important", Log.Debug).minimumLevel(Log.Information).createLogger()65 overridden.forSource("App.Noisy.Cache").information("noisy info", [])66 overridden.forSource("App.Noisy.Cache").warning("noisy warning", [])67 overridden.forSource("app.noisy.important").debug("important debug", [])68 overridden.forSource("App.NoisyNeighbour").information("neighbour info", [])69 overridden.information("plain info", [])70 let truncatingMemory = Memory.create()71 let truncating = base(&truncatingMemory).overrideLevel("App.Noisy", Log.Error).maximumStringLength(4).createLogger()72 truncating.forSource("App.Noisy.Cache").warning("truncated source warning", [])7374 let contextMemory = Memory.create()75 let noLines: Array[Str] = []76 let lines = Sync.cell(noLines)77 let contextual = base(&contextMemory).enrichWithProperty("App", Value.text("demo")).selfLog(&collecting(&lines)).createLogger()78 let job = contextual.forContext("JobId", Value.int(7)).forContext("JobId", Value.int(8))79 job.information("Job \{Step\}", [Value.text("start")])80 job.information("Explicit \{JobId\}", [Value.int(1)])81 let unchanged = contextual.forContext(" ", Value.int(1))82 unchanged.information("still", [])83 contextual.information("Missing \{A\} \{B\}", [Value.int(1)])8485 let checks = Test.suite("Logger", &[86 Test.equals("an event is written with its parts", &(first.level, first.timestamp, first.template.text, first.failure), &(Log.Information, NOW, "Disk \{Disk\} is \{Percent\}% full", None)),87 Test.equals("its message renders the values", &Event.message(&first), &"Disk \"C\" is 93% full"),88 Test.equals("events below the minimum are dropped", &Memory.events(&memory).length(), &1),89 Test.that("isEnabled follows the minimum", logger.isEnabled(Log.Information) && !logger.isEnabled(Log.Debug)),90 Test.equals("a level switch changes what is written", &Memory.messages(&switchMemory), &["after"]),91 Test.equals("a listener hears each real change once", &Sync.get(&changes), &Ok(1)),92 Test.equals("overrides decide for their sources", &Memory.messages(&overrideMemory), &["noisy warning", "important debug", "neighbour info", "plain info"]),93 Test.equals("overrides match the source as given, before truncation", &Memory.messages(&truncatingMemory), &[]),94 Test.equals("the source context is a property", &Event.sourceContext(&Memory.events(&overrideMemory)[0]), &Some("App.Noisy.Cache")),95 Test.equals("context properties join the event", &Event.property(&Memory.events(&contextMemory)[0], "JobId"), &Some(Value.int(8))),96 Test.equals("pipeline enrichers add properties", &Event.property(&Memory.events(&contextMemory)[0], "App"), &Some(Value.text("demo"))),97 Test.equals("message properties win over context", &Event.property(&Memory.events(&contextMemory)[1], "JobId"), &Some(Value.int(1))),98 Test.equals("a blank context name leaves the logger unchanged", &Memory.events(&contextMemory)[2].properties.length(), &1),99 Test.that("a blank context name is reported", linesOf(&lines).length() >= 1 && linesOf(&lines)[0].contains("invalid name")),100 Test.that("binding problems are reported", linesOf(&lines).length() == 2 && linesOf(&lines)[1].contains("Named property count does not match parameter count: Missing"))101 ])102 let ran = Test.run(&checks)103 for failure in Test.failuresOf(&ran) {104 let _reported = Io.writeErrorLine(failure)105 }106 Test.report(&ran)107}108