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

Json.pudu

Pudu59 lines2.9 KB

GitHub ↗
1/** @Log.Formatting.Json.Formatter — events as JSON documents */2module PuduLangLog.Formatting.Json34import Std.Option as Option5import PuduLangLog.Domain.Dates as Dates6import PuduLangLog.Domain.Display as Display7import PuduLangLog.Domain.Json as Json8import PuduLangLog.Domain.Levels as Levels9import PuduLangLog as Log1011/** @Log.Formatting.Json.Options — message rendering and line ending */12export type Options = { renderMessage: Bool, closingDelimiter: Str }1314/// The name the plain JSON formatter gives a structure's type tag.15const TYPE_TAG: Str = "_typeTag"1617/// The template only, each document ending in a line break.18export fn defaults() -> Options { Options{renderMessage: false, closingDelimiter: "\n"} }1920/// A formatter writing each event as one JSON document of `Timestamp`, `Level`, `MessageTemplate`,21/// optionally `RenderedMessage`, `TraceId`, `SpanId`, `Exception`, `Properties`, and the22/// `Renderings` of formatted holes grouped by property.23export fn formatter(options: Options) -> Log.Formatter {24  fn(event: &Log.Event) -> Str {25    var members = [26      "\"Timestamp\":" + Json.quote(Dates.roundTrip(&event.timestamp)),27      "\"Level\":" + Json.quote(Levels.name(event.level)),28      "\"MessageTemplate\":" + Json.quote(event.template.text)29    ]30    if options.renderMessage { members = members.push("\"RenderedMessage\":" + Json.quote(Display.plain(&Display.message(&event.template, &event.properties, &None)))) }31    if let Some(trace) = event.traceId { members = members.push("\"TraceId\":" + Json.quote(trace)) }32    if let Some(span) = event.spanId { members = members.push("\"SpanId\":" + Json.quote(span)) }33    if let Some(problem) = event.failure { members = members.push("\"Exception\":" + Json.quote(Display.failure(&problem))) }34    if !event.properties.isEmpty() {35      let properties = event.properties.map(|property: Log.Property| Json.quote(property.name) + ":" + Json.value(&property.value, &Some(TYPE_TAG)))36      members = members.push("\"Properties\":\{" + properties.join(",") + "\}")37    }38    let renderings = renderingsOf(event)39    if !renderings.isEmpty() { members = members.push("\"Renderings\":\{" + renderings.join(",") + "\}") }40    "\{" + members.join(",") + "\}" + options.closingDelimiter41  }42}4344/// Each formatted hole's rendering, grouped by property name in order of first appearance.45fn renderingsOf(event: &Log.Event) -> Array[Str] {46  let formatted = event.template.holes.filter(|hole: Log.Hole| hole.format != None)47  var names: Array[Str] = []48  for hole in formatted {49    if !names.contains(hole.name) { names = names.push(hole.name) }50  }51  names.map(|name: Str| {52      let entries = formatted.filter(|hole: Log.Hole| hole.name == name).map(|hole: Log.Hole| {53          let rendering = Display.plain(&Display.hole(&hole, &event.properties, true, false))54          "\{\"Format\":" + Json.quote(Option.unwrapOr(hole.format, "")) + ",\"Rendering\":" + Json.quote(rendering) + "\}"55        })56      Json.quote(name) + ":[" + entries.join(",") + "]"57    })58}59