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

Pudu90 lines3.3 KB

GitHub ↗
1/** @Log.Domain.Json.Module — captured values written as JSON text */2module PuduLangLog.Domain.Json34import Std.Decimal as Decimal5import Std.Option as Option6import PuduLangLog.Domain.Dates as Dates7import PuduLangLog.Domain.Numbers as Numbers8import PuduLangLog as Log910/// Hexadecimal digits by value, upper case.11const HEX: Str = "0123456789ABCDEF"1213/// The text as a JSON string literal: quotes, backslashes, and control characters escaped.14export fn quote(text: Str) -> Str {15  var pieces: Array[Str] = ["\""]16  for character in text.chars() {17    let code = character.code()18    if character == '"' {19      pieces = pieces.push("\\\"")20    } else if character == '\\' {21      pieces = pieces.push("\\\\")22    } else if character == '\n' {23      pieces = pieces.push("\\n")24    } else if character == '\r' {25      pieces = pieces.push("\\r")26    } else if character == '\t' {27      pieces = pieces.push("\\t")28    } else if code == 12 {29      pieces = pieces.push("\\f")30    } else if code < 32 {31      pieces = pieces.push("\\u00" + HEX.slice(code / 16, code / 16 + 1) + HEX.slice(code % 16, code % 16 + 1))32    } else {33      pieces = pieces.push(character.toText())34    }35  }36  pieces.push("\"").join("")37}3839/// A scalar as JSON: numbers bare, text and times quoted, and non-finite floats as quoted names.40export fn scalar(atom: &Log.Scalar) -> Str {41  match atom {42    case Log.Null => "null"43    case Log.Boolean(flag) => if flag { "true" } else { "false" }44    case Log.Integer(number) => show(number)45    case Log.Real(number) => if Numbers.digitsOfReal(number) == None { quote(show(number)) } else { Numbers.realText(number) }46    case Log.Exact(number) => Decimal.toText(number)47    case Log.Text(text) => quote(text)48    case Log.Moment(moment) => quote(Dates.roundTrip(&moment))49    case Log.Span(millis) => quote(Dates.formatSpan(millis, &None))50    case Log.Binary(bytes) => quote(hexOf(&bytes))51  }52}5354/// A value as JSON. Structures become objects that carry their tag under `typeTag` when one is55/// named; dictionaries become objects keyed by their keys' text.56export fn value(held: &Log.Value, typeTag: &Option[Str]) -> Str {57  match held {58    case Log.Scalar(atom) => scalar(&atom)59    case Log.Sequence(items) => "[" + items.map(|item: Log.Value| value(&item, typeTag)).join(",") + "]"60    case Log.Structure(shape) => {61      var members = shape.properties.map(|property: Log.Property| quote(property.name) + ":" + value(&property.value, typeTag))62      if let (Some(name), Some(tag)) = (*typeTag, shape.tag) { members = members.push(quote(name) + ":" + quote(tag)) }63      "\{" + members.join(",") + "\}"64    }65    case Log.Dictionary(entries) => "\{" + entries.map(|entry: Log.Entry| keyOf(&entry.key) + ":" + value(&entry.value, typeTag)).join(",") + "\}"66  }67}6869/// A dictionary key as a JSON string.70fn keyOf(key: &Log.Scalar) -> Str {71  match key {72    case Log.Text(text) => quote(text)73    case Log.Null => "\"null\""74    case _ => {75      let text = scalar(key)76      if text.startsWith("\"") { text } else { quote(text) }77    }78  }79}8081/// Bytes as upper-case hexadecimal digits.82export fn hexOf(bytes: &Bytes) -> Str {83  var pieces: Array[Str] = []84  for octet in bytes.toArray() {85    let code = Option.unwrapOr(convertInteger[Int](octet), 0)86    pieces = pieces.push(HEX.slice(code / 16, code / 16 + 1) + HEX.slice(code % 16, code % 16 + 1))87  }88  pieces.join("")89}90