OxL Reference

Configuration and observability

Typed configuration#

Task. Program parameters - port, name, debug mode - with defaults, profiles and an override from outside.

oxl✓ checked in CI
config App {
  port: u16 = 8080
  name: str = "demo"
  debug: bool = false
  profile dev { debug = true }  // the dev profile is on by default
  profile prod { port = 443 }
}

fn main() io {
  let app = App.get()  // the value is fixed for the lifetime of the node
  print("{app.name} listens on {app.port}, debug={app.debug}")
}
Output
demo listens on 8080, debug=true
  • config App { … } is a typed config: fields like data, default values, profile blocks. App.get() returns an immutable value fixed for the whole run.
  • Sources in descending priority: the flag --set App.port=8080 -> the environment variable APP_PORT -> the file config/<profile>.toml -> a profile block -> the default. Try: APP_PORT=9000 oxl run prog.oxl or oxl run prog.oxl --profile prod.
  • A wrong type or a violated refinement (port: Port with a range) gives a message naming the source and exit code 78 before the program starts: an incorrect configuration does not live to see the first request.
  • Declare secrets with the type secret<str>: they do not get into logs and are not serialized.
  • Values that must change without a restart (limits, flags) are described by dynamic (§14.2).

Logs, metrics, spans#

Task. Record events, count requests and time sections of code.

oxl✓ checked in CI
use std.obs { log, Level, metric, span }

fn handle(id: i64) -> i64  io {
  log.info("handling request {id}")  // records go to stderr, not stdout
  id * 2
}

fn main() io {
  log.setLevel(Level.Info)
  let served = metric.counter("requests_total", "requests served")
  let inflight = metric.gauge("inflight", "requests in flight")
  var total = 0
  for id in 1..=3 {
    inflight.inc()
    total += span.with("handle", () => handle(id))  // a span times a section and joins the trace
    served.inc()
    inflight.dec()
  }
  log.debug("not printed: level is Info")
  print("total {total}, served {served.value()}, in flight {inflight.value()}")
  print("debug enabled: {log.enabled(Level.Debug)}")
}
Output
total 12, served 3.0, in flight 0.0
debug enabled: false
  • log.info("…") writes a record to stderr (only the program output stays in stdout); the level is set by log.setLevel(Level.Info) or the run flag --log-level. Records contain the time, the level and fields; log.setJson(true) switches to JSON for log collectors.
  • metric.counter("requests_total", "…"), gauge, histogram are node metrics; in a cluster oxl inspect shows them.
  • span.with("handle", () => …) times a section and nests into the request trace; in services the trace is propagated between nodes automatically (traceId in ctx).
  • If a message is expensive to build, check log.enabled(Level.Debug) first.

Back: cookbook.

See also

OxL 1.0.0-beta · Documentation version 1.0.0-beta© OxwaldLink · generated from the Standard, the std sources and the guides