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

Output.pudu

Pudu88 lines3.8 KB

GitHub ↗
1/** @Log.Domain.Output.Module — output templates rendered from events */2module PuduLangLog.Domain.Output34import Std.List as List5import PuduLangLog.Domain.Display as Display6import PuduLangLog.Domain.Levels as Levels7import PuduLangLog.Domain.Padding as Padding8import PuduLangLog.Domain.Properties as Properties9import PuduLangLog as Log1011/// The line break the `NewLine` hole writes.12export const NEW_LINE: Str = "\n"1314/// The event rendered through an output template. `Timestamp`, `UtcTimestamp`, `Level`,15/// `Message`, `Exception`, `NewLine`, `Properties`, `TraceId`, and `SpanId` name parts of the16/// event; any other hole names a property and writes nothing when it is absent.17export fn render(output: &Log.Template, event: &Log.Event) -> Array[Display.Piece] {18  var pieces: Array[Display.Piece] = []19  for token in output.tokens {20    match token {21      case Log.Literal(text) => { pieces = pieces.push(Display.Piece{style: Display.Tertiary, text: text}) }22      case Log.Placeholder(hole) => { pieces = pieces.concat(Display.aligned(&part(output, &hole, event), &hole.alignment)) }23    }24  }25  pieces26}2728/// The pieces one output hole stands for, before alignment.29fn part(output: &Log.Template, hole: &Log.Hole, event: &Log.Event) -> Array[Display.Piece] {30  match hole.name {31    case "Level" => [Display.Piece{style: Display.LevelStyle(event.level), text: Levels.moniker(event.level, &hole.format)}]32    case "Message" => Display.message(&event.template, &event.properties, &hole.format)33    case "Timestamp" => [secondary(Display.scalar(&Log.Moment(event.timestamp), &hole.format).text)]34    case "UtcTimestamp" => [secondary(Display.scalar(&Log.Moment(Log.Timestamp{..event.timestamp, offset: 0}), &hole.format).text)]35    case "NewLine" => [plain(NEW_LINE)]36    case "Exception" => failure(&event.failure)37    case "TraceId" => [plain(optional(&event.traceId))]38    case "SpanId" => [plain(optional(&event.spanId))]39    case "Properties" => properties(output, event, &hole.format)40    case name => match Properties.find(&event.properties, name) {41      case Some(Log.Scalar(Log.Text(text))) => [Display.Piece{style: Display.StringStyle, text: Padding.cased(text, &hole.format)}]42      case Some(found) => Display.value(&found, &hole.format)43      case None => []44    }45  }46}4748/// Plain text.49fn plain(text: Str) -> Display.Piece { Display.Piece{style: Display.Plain, text: text} }5051/// Secondary text.52fn secondary(text: Str) -> Display.Piece { Display.Piece{style: Display.Secondary, text: text} }5354/// Optional text, empty when absent.55fn optional(text: &Option[Str]) -> Str {56  match text {57    case Some(given) => given58    case None => ""59  }60}6162/// A failure's text followed by a line break, trace lines in the secondary style; nothing when63/// the event has no failure.64fn failure(problem: &Option[Log.Failure]) -> Array[Display.Piece] {65  let found = match problem {66    case Some(given) => given67    case None => { return [] }68  }69  Display.failure(&found).split("\n").map(|line: Str| if line.startsWith("   at ") { secondary(line + NEW_LINE) } else { plain(line + NEW_LINE) })70}7172/// The properties named neither in the message template nor in the output template, as a73/// structure, or as a JSON object when the format contains `j`.74fn properties(output: &Log.Template, event: &Log.Event, format: &Option[Str]) -> Array[Display.Piece] {75  let rest = event.properties.filter(|property: Log.Property| !mentions(&event.template, property.name) && !mentions(output, property.name))76  let shape = Log.Structure(Log.Structure{tag: None, properties: rest})77  let asJson = match format {78    case Some(given) => given.contains("j")79    case None => false80  }81  if asJson { [Display.json(&shape)] } else { Display.value(&shape, &None) }82}8384/// Whether a template has a hole of the name.85fn mentions(template: &Log.Template, name: Str) -> Bool {86  List.any(&template.holes, |hole: Log.Hole| hole.name == name)87}88