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

MapTest.pudu

Pudu94 lines4.7 KB

GitHub ↗
1/** @Test.Sinks.Map.Suite — routing events to one sink per key */2module PuduLangLog.Sinks.MapTest34import Std.Io as Io5import Std.List as List6import Std.Sync as Sync7import Std.Test as Test8import PuduLangLog.Configuration as Configuration9import PuduLangLog as Log10import PuduLangLog.Logger as Logger11import PuduLangLog.Sink as Sink12import PuduLangLog.Sinks.Map as Map13import PuduLangLog.Value as Value1415/// A sink recording `key:message`, `key:flush`, and `key:close` into a shared journal.16fn journaled(journal: &Sync.Cell[Array[Str]], key: Str) -> Sink.Sink {17  let shared = *journal18  Sink.Sink {19    emit: fn(event: &Log.Event) -> Result[(), Str] {20      note(&shared, key + ":" + event.template.text)21      if key == "broken" { Err("refused") } else { Ok(()) }22    },23    flush: fn() -> () { note(&shared, key + ":flush") },24    close: fn() -> () { note(&shared, key + ":close") },25    attach: fn(_listener: Sink.Listener) -> () { note(&shared, key + ":attach") }26  }27}2829/// Appends a line to the journal.30fn note(journal: &Sync.Cell[Array[Str]], line: Str) -> () {31  let current = match Sync.get(journal) {32    case Ok(lines) => lines33    case Err(_) => []34  }35  let _stored = Sync.set(journal, current.push(line))36}3738/// The journal's lines.39fn linesOf(journal: &Sync.Cell[Array[Str]]) -> Array[Str] {40  match Sync.get(journal) {41    case Ok(lines) => lines42    case Err(_) => []43  }44}4546/// Runs the suite.47fn main() -> Int {48  let none: Array[Str] = []49  let journal = Sync.cell(none)50  let routed = Map.sink(Map.defaults(Map.byProperty("Tenant", "shared"), fn(key: Str) -> Sink.Sink { journaled(&journal, key) }))51  let logger = Configuration.create().writeTo(routed).createLogger()52  logger.information("\{Tenant\} a", [Value.text("north")])53  logger.information("\{Tenant\} b", [Value.text("south")])54  logger.information("\{Tenant\} c", [Value.text("north")])55  logger.information("no tenant", [])56  logger.information("\{Tenant\} d", [Value.int(7)])57  let routedLines = linesOf(&journal)58  Logger.close(&logger)59  let closedLines = linesOf(&journal)60  let limited = Sync.cell(none)61  let bounded = Map.sink(Map.Options{..Map.defaults(Map.byProperty("Tenant", "shared"), fn(key: Str) -> Sink.Sink { journaled(&limited, key) }), limit: Some(2)})62  let boundedLogger = Configuration.create().writeTo(bounded).createLogger()63  for tenant in ["a", "b", "a", "c", "b"] { boundedLogger.information("\{Tenant\}", [Value.text(tenant)]) }64  let eager = Sync.cell(none)65  let single = Map.sink(Map.Options{..Map.defaults(Map.byProperty("Tenant", "shared"), fn(key: Str) -> Sink.Sink { journaled(&eager, key) }), limit: Some(0)})66  let _one = (single.emit)(&Log.Event{..sampleEvent(), properties: [Value.property("Tenant", Value.text("x"))]})67  let failing = Map.sink(Map.defaults(fn(_event: &Log.Event) -> Str { "broken" }, fn(key: Str) -> Sink.Sink { journaled(&journal, key) }))68  let refused = (failing.emit)(&sampleEvent())69  let after = Sync.cell(none)70  let closing = Map.sink(Map.defaults(fn(_event: &Log.Event) -> Str { "k" }, fn(key: Str) -> Sink.Sink { journaled(&after, key) }))71  (closing.attach)(fn(_report: &Sink.Report) -> () {})72  let _first = (closing.emit)(&sampleEvent())73  (closing.close)()74  let _late = (closing.emit)(&sampleEvent())75  let checks = Test.suite("Sinks.Map", &[76      Test.equals("events reach their key's sink, opened once", &routedLines, &["north:\{Tenant\} a", "south:\{Tenant\} b", "north:\{Tenant\} c", "shared:no tenant", "7:\{Tenant\} d"]),77      Test.equals("closing flushes, then closes every open sink, least recent first", &List.drop(&closedLines, routedLines.length()), &["south:flush", "north:flush", "shared:flush", "7:flush", "south:flush", "south:close", "north:flush", "north:close", "shared:flush", "shared:close", "7:flush", "7:close"]),78      Test.equals("the least recently used sink closes beyond the limit", &linesOf(&limited), &["a:\{Tenant\}", "b:\{Tenant\}", "a:\{Tenant\}", "c:\{Tenant\}", "b:flush", "b:close", "b:\{Tenant\}", "a:flush", "a:close"]),79      Test.equals("a limit of zero closes each sink after its event", &linesOf(&eager), &["x:msg", "x:flush", "x:close"]),80      Test.equals("a failing sink's error reaches the caller", &refused, &Err("refused")),81      Test.equals("listeners reach sinks opened later, and a closed map drops events", &linesOf(&after), &["k:attach", "k:msg", "k:flush", "k:close"])82    ])83  let ran = Test.run(&checks)84  for failure in Test.failuresOf(&ran) {85    let _reported = Io.writeErrorLine(failure)86  }87  Test.report(&ran)88}8990/// An event with no properties.91fn sampleEvent() -> Log.Event {92  Log.Event{timestamp: Log.Timestamp{millis: 0, offset: 0}, level: Log.Information, template: Log.Template{text: "msg", tokens: [Log.Literal("msg")], holes: [], binding: Log.Unbound}, properties: [], failure: None, traceId: None, spanId: None}93}94