Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-log

Structured event logging for Pudu: message templates, enrichment, filtering, formatting, and sinks

0.1.0Apache-2.01

InstallClose

LoggerTest.pudu

Pudu108 lines5.5 KB

GitHub ↗
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 Value1617/// The moment every event in this suite carries.18const NOW: Log.Timestamp = Log.Timestamp{millis: 1790604309123, offset: 120}1920/// A configuration writing to a memory store with a fixed clock.21fn base(memory: &Memory.Memory) -> Configuration.Configuration {22  Configuration.create().clock(Clock.fixed(NOW)).writeTo(Memory.sink(memory))23}2425/// A self-log collecting its lines.26fn 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}3435/// The lines a cell holds.36fn linesOf(lines: &Sync.Cell[Array[Str]]) -> Array[Str] {37  match Sync.get(lines) {38    case Ok(held) => held39    case Err(_) => []40  }41}4243/// Runs the suite.44fn 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