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

OutputTest.pudu

Pudu60 lines4.5 KB

GitHub ↗
1/** @Test.Domain.Output.Suite — output templates and event identifiers */2module PuduLangLog.Domain.OutputTest34import Std.Io as Io5import Std.Test as Test6import PuduLangLog.Domain.Display as Display7import PuduLangLog.Domain.EventId as EventId8import PuduLangLog.Domain.Output as Output9import PuduLangLog.Domain.Parser as Parser10import PuduLangLog as Log1112/// A text scalar value.13fn text(held: Str) -> Log.Value { Log.Scalar(Log.Text(held)) }1415/// A property.16fn property(name: Str, held: Log.Value) -> Log.Property { Log.Property{name: name, value: held} }1718/// The event rendered through an output template, as plain text.19fn rendered(output: Str, event: &Log.Event) -> Str { Display.plain(&Output.render(&Parser.parse(output), event)) }2021/// Runs the suite.22fn main() -> Int {23  let event = Log.Event {24    timestamp: Log.Timestamp{millis: 1790604309123, offset: 120},25    level: Log.Warning,26    template: Parser.parse("Disk \{Disk\} is \{Percent\}% full"),27    properties: [property("Disk", text("C")), property("Percent", Log.Scalar(Log.Integer(93))), property("SourceContext", text("App.Disk")), property("Host", text("web-1"))],28    failure: None,29    traceId: Some("4bf92f3577b34da6a3ce929d0e0e4736"),30    spanId: Some("00f067aa0ba902b7")31  }32  let failed = Log.Event{..event, failure: Some(Log.Failure{kind: "IoError", message: "full", trace: ["Disk.write"], causes: []})}33  let checks = Test.suite("Domain.Output", &[34      Test.equals("the console layout", &rendered("[\{Timestamp:HH:mm:ss\} \{Level:u3\}] \{Message:lj\}\{NewLine\}\{Exception\}", &event), &"[16:05:09 WRN] Disk C is 93% full\n"),35      Test.equals("the file layout", &rendered("\{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz\} [\{Level:u3\}] \{Message:lj\}\{NewLine\}\{Exception\}", &event), &"2026-09-28 16:05:09.123 +02:00 [WRN] Disk C is 93% full\n"),36      Test.equals("a message without format quotes text", &rendered("\{Message\}", &event), &"Disk \"C\" is 93% full"),37      Test.equals("timestamps without a format", &(rendered("\{Timestamp\}", &event), rendered("\{UtcTimestamp:HH:mm\}", &event)), &("09/28/2026 16:05:09 +02:00", "14:05")),38      Test.equals("levels", &(rendered("\{Level\}", &event), rendered("\{Level:w3\}", &event), rendered("[\{Level,-9\}]", &event)), &("Warning", "wrn", "[Warning  ]")),39      Test.equals("text properties render bare and cased", &(rendered("\{SourceContext\}", &event), rendered("\{Host:u\}", &event)), &("App.Disk", "WEB-1")),40      Test.equals("other properties render as values", &rendered("\{Percent:000\}", &event), &"093"),41      Test.equals("a missing property writes nothing", &rendered("[\{Missing,5\}]", &event), &"[     ]"),42      Test.equals("trace and span", &rendered("\{TraceId\}/\{SpanId\}", &event), &"4bf92f3577b34da6a3ce929d0e0e4736/00f067aa0ba902b7"),43      Test.equals("absent trace and span are empty", &rendered("[\{TraceId\}]", &Log.Event{..event, traceId: None}), &"[]"),44      Test.equals("the remaining properties", &rendered("\{Properties\}", &event), &"\{ SourceContext: \"App.Disk\", Host: \"web-1\" \}"),45      Test.equals("properties named by the output template are left out", &rendered("\{SourceContext\} \{Properties\}", &event), &"App.Disk \{ Host: \"web-1\" \}"),46      Test.equals("the remaining properties as JSON", &rendered("\{Properties:j\}", &event), &"\{\"SourceContext\":\"App.Disk\",\"Host\":\"web-1\"\}"),47      Test.equals("no remaining properties", &rendered("\{Properties\}", &Log.Event{..event, properties: []}), &"\{ \}"),48      Test.equals("a failure ends with a line break", &rendered("\{Message:l\}\{NewLine\}\{Exception\}", &failed), &"Disk C is 93% full\nIoError: full\n   at Disk.write\n"),49      Test.equals("trace lines are secondary", &Output.render(&Parser.parse("\{Exception\}"), &failed).map(|piece: Display.Piece| piece.style), &[Display.Plain, Display.Secondary]),50      Test.equals("output styles", &Output.render(&Parser.parse("[\{Timestamp:HH\} \{Level:u3\}]"), &event).map(|piece: Display.Piece| piece.style), &[Display.Tertiary, Display.Secondary, Display.Tertiary, Display.LevelStyle(Log.Warning), Display.Tertiary]),51      Test.equals("event identifiers", &[EventId.hex(EventId.compute("Hello, \{Name\}!")), EventId.hex(EventId.compute("")), EventId.hex(EventId.compute("a"))], &["3872171f", "00000000", "ca2e9442"]),52      Test.equals("a long template's identifier", &EventId.hex(EventId.compute("HTTP \{RequestMethod\} \{RequestPath\} responded \{StatusCode\} in \{Elapsed:0.0000\} ms")), &"62d0885c")53    ])54  let ran = Test.run(&checks)55  for failure in Test.failuresOf(&ran) {56    let _reported = Io.writeErrorLine(failure)57  }58  Test.report(&ran)59}60