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

Environment.pudu

Pudu105 lines3.9 KB

GitHub ↗
1/** @Log.Enrichers.Environment.Module — machine, user, process, and failure properties */2module PuduLangLog.Enrichers.Environment34import Std.Env as Env5import Std.List as List6import Std.Option as Option7import Std.Process as Process8import Std.Result as Result9import PuduLangLog.Enricher as Enricher10import PuduLangLog as Log1112/// The variable `environmentName` reads when not told otherwise.13export const ENVIRONMENT_VARIABLE: Str = "PUDU_ENVIRONMENT"1415/// The environment name used when the variable is not set.16const PRODUCTION: Str = "Production"1718/// Adds `MachineName`: the `HOSTNAME` or `COMPUTERNAME` variable, or the `hostname` command's answer,19/// read once.20export fn machineName() -> Enricher.Enricher {21  let name = match firstVariable(["HOSTNAME", "COMPUTERNAME"]) {22    case Some(found) => found23    case None => Result.unwrapOr(Process.output("hostname", &[]), "").trim()24  }25  Enricher.property("MachineName", Log.Scalar(Log.Text(name)))26}2728/// Adds `EnvironmentUserName`: the `USER` or `USERNAME` variable, read once.29export fn environmentUserName() -> Enricher.Enricher {30  let name = match firstVariable(["USER", "USERNAME"]) {31    case Some(found) => found32    case None => ""33  }34  Enricher.property("EnvironmentUserName", Log.Scalar(Log.Text(name)))35}3637/// Adds `EnvironmentName` from a variable, `Production` when it is unset or empty.38export fn environmentName(variable: Str) -> Enricher.Enricher {39  let name = match firstVariable([variable]) {40    case Some(found) => found41    case None => PRODUCTION42  }43  Enricher.property("EnvironmentName", Log.Scalar(Log.Text(name)))44}4546/// Adds `ProcessId`, read once from the parent of a short-lived shell.47export fn processId() -> Enricher.Enricher {48  let answer = Result.unwrapOr(Process.output("sh", &["-c", "echo $PPID"]), "").trim()49  let held = match answer.toInt() {50    case Some(number) => Log.Scalar(Log.Integer(number))51    case None => Log.Scalar(Log.Null)52  }53  Enricher.property("ProcessId", held)54}5556/// Adds `ProcessName`, the command name of the running program, read once.57export fn processName() -> Enricher.Enricher {58  let answer = Result.unwrapOr(Process.output("sh", &["-c", "ps -o comm= -p $PPID"]), "").trim()59  let name = Option.unwrapOr(List.last(&answer.split("/")), answer)60  Enricher.property("ProcessName", if name.isEmpty() { Log.Scalar(Log.Null) } else { Log.Scalar(Log.Text(name)) })61}6263/// Adds a property named after an environment variable holding its value, read once; an unset64/// variable adds nothing.65export fn environmentVariable(variable: Str) -> Enricher.Enricher {66  match Env.variable(variable) {67    case Some(found) => Enricher.property(variable, Log.Scalar(Log.Text(found)))68    case None => Enricher.all([])69  }70}7172/// Adds `FailureDetail`, a structure of the event's failure — `Kind`, `Message`, `Trace`, and73/// `Causes` — for sinks that store properties but not failure text.74export fn failureDetail() -> Enricher.Enricher {75  Enricher.computed("FailureDetail", fn(event: &Log.Event) -> Log.Value {76      match event.failure {77        case Some(problem) => detailOf(&problem)78        case None => Log.Scalar(Log.Null)79      }80    }, true)81}8283/// A failure as a structure.84fn detailOf(problem: &Log.Failure) -> Log.Value {85  Log.Structure(Log.Structure {86      tag: Some(problem.kind),87      properties: [88        Log.Property{name: "Kind", value: Log.Scalar(Log.Text(problem.kind))},89        Log.Property{name: "Message", value: Log.Scalar(Log.Text(problem.message))},90        Log.Property{name: "Trace", value: Log.Sequence(problem.trace.map(|line: Str| Log.Scalar(Log.Text(line))))},91        Log.Property{name: "Causes", value: Log.Sequence(problem.causes.map(|cause: Log.Failure| detailOf(&cause)))}92      ]93    })94}9596/// The first of the variables that is set and not empty.97fn firstVariable(names: Array[Str]) -> Option[Str] {98  for name in names {99    if let Some(found) = Env.variable(name) {100      if !found.trim().isEmpty() { return Some(found.trim()) }101    }102  }103  None104}105